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 4cb2e9e
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 55 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
73 changes: 45 additions & 28 deletions lib/rules/no-deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,14 @@
*/
'use strict';

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

// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------

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

var DEPRECATED = {
MemberExpression: {
// 0.12.0
'React.renderComponent': ['0.12.0', 'React.render'],
'React.renderComponentToString': ['0.12.0', 'React.renderToString'],
'React.renderComponentToStaticMarkup': ['0.12.0', 'React.renderToStaticMarkup'],
'React.isValidComponent': ['0.12.0', 'React.isValidElement'],
'React.PropTypes.component': ['0.12.0', 'React.PropTypes.element'],
'React.PropTypes.renderable': ['0.12.0', 'React.PropTypes.node'],
'React.isValidClass': ['0.12.0'],
'this.transferPropsTo': ['0.12.0', 'spread operator ({...})'],
// 0.13.0
'React.addons.classSet': ['0.13.0', 'the npm module classnames'],
'React.addons.cloneWithProps': ['0.13.0', 'React.cloneElement'],
// 0.14.0
'React.render': ['0.14.0', 'ReactDOM.render'],
'React.unmountComponentAtNode': ['0.14.0', 'ReactDOM.unmountComponentAtNode'],
'React.findDOMNode': ['0.14.0', 'ReactDOM.findDOMNode'],
'React.renderToString': ['0.14.0', 'ReactDOMServer.renderToString'],
'React.renderToStaticMarkup': ['0.14.0', 'ReactDOMServer.renderToStaticMarkup']
}
};

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand All @@ -48,6 +27,37 @@ module.exports = function(context) {
return Number(part);
});

var pragma = pragmaUtil.getFromContext(context);

function getDeprecated() {
var deprecated = {
MemberExpression: {}
};
// 0.12.0
deprecated.MemberExpression[pragma + '.renderComponent'] = ['0.12.0', pragma + '.render'];
deprecated.MemberExpression[pragma + '.renderComponentToString'] = ['0.12.0', pragma + '.renderToString'];
deprecated.MemberExpression[pragma + '.renderComponentToStaticMarkup'] = [
'0.12.0',
pragma + '.renderToStaticMarkup'
];
deprecated.MemberExpression[pragma + '.isValidComponent'] = ['0.12.0', pragma + '.isValidElement'];
deprecated.MemberExpression[pragma + '.PropTypes.component'] = ['0.12.0', pragma + '.PropTypes.element'];
deprecated.MemberExpression[pragma + '.PropTypes.renderable'] = ['0.12.0', pragma + '.PropTypes.node'];
deprecated.MemberExpression[pragma + '.isValidClass'] = ['0.12.0'];
deprecated.MemberExpression['this.transferPropsTo'] = ['0.12.0', 'spread operator ({...})'];
// 0.13.0
deprecated.MemberExpression[pragma + '.addons.classSet'] = ['0.13.0', 'the npm module classnames'];
deprecated.MemberExpression[pragma + '.addons.cloneWithProps'] = ['0.13.0', pragma + '.cloneElement'];
// 0.14.0
deprecated.MemberExpression[pragma + '.render'] = ['0.14.0', 'ReactDOM.render'];
deprecated.MemberExpression[pragma + '.unmountComponentAtNode'] = ['0.14.0', 'ReactDOM.unmountComponentAtNode'];
deprecated.MemberExpression[pragma + '.findDOMNode'] = ['0.14.0', 'ReactDOM.findDOMNode'];
deprecated.MemberExpression[pragma + '.renderToString'] = ['0.14.0', 'ReactDOMServer.renderToString'];
deprecated.MemberExpression[pragma + '.renderToStaticMarkup'] = ['0.14.0', 'ReactDOMServer.renderToStaticMarkup'];

return deprecated;
}

function checkVersion(methodVer) {
methodVer = methodVer.split('.').map(function(part) {
return Number(part);
Expand All @@ -60,10 +70,12 @@ module.exports = function(context) {
}

function isDeprecated(type, method) {
var deprecated = getDeprecated();

return (
DEPRECATED[type] &&
DEPRECATED[type][method] &&
checkVersion(DEPRECATED[type][method][0])
deprecated[type] &&
deprecated[type][method] &&
checkVersion(deprecated[type][method][0])
);
}

Expand All @@ -78,11 +90,16 @@ module.exports = function(context) {
if (!isDeprecated(node.type, method)) {
return;
}
var deprecated = getDeprecated();
context.report(node, DEPRECATED_MESSAGE, {
oldMethod: method,
version: DEPRECATED[node.type][method][0],
newMethod: DEPRECATED[node.type][method][1] ? ', use ' + DEPRECATED[node.type][method][1] + ' instead' : ''
version: deprecated[node.type][method][0],
newMethod: deprecated[node.type][method][1] ? ', use ' + deprecated[node.type][method][1] + ' instead' : ''
});
},

BlockComment: function(node) {
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}
]
});
Loading

0 comments on commit 4cb2e9e

Please sign in to comment.