Skip to content

Commit

Permalink
Merge branch 'master' of github.com:yannickcr/eslint-plugin-react
Browse files Browse the repository at this point in the history
* 'master' of github.com:yannickcr/eslint-plugin-react:
  Add require-extension rule to Readme
  Add acceptTranspilerName option to display-name rule (fixes jsx-eslint#75)
  Added support for prop-types check of type `this.props['this-format']`. Used list of nested names instead of dot-separated names to avoid conflict with properties like `this.props['this.format']`
  Fix crash if a ClassProperty has only one token (fixes jsx-eslint#125)
  Fix `prop-types` desctructuring with properties as string.
  Added support for prop type identified by strings in rule `jsx-sort-prop-types`
  fix detection of missing propTypes validations on ecmaFeatures.jsx false
  fix detect missing displayName in React class when ecmaFeatures.jsx is false
  Allow blocking of '.jsx' extensions with `require()`
  Point directly to mocha instead of symlink in .bin. On Windows the file in .bin is not a symlink. Inspired by solution in eslint
  Support nested prop types and use react propTypes to make further analysis. Minimal analyse of primitive propTypes.
  • Loading branch information
NickStefan committed Jun 22, 2015
2 parents e78e5b9 + f6292bb commit caf03ce
Show file tree
Hide file tree
Showing 13 changed files with 1,275 additions and 36 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Finally, enable all of the rules that you would like to use.
"react/no-unknown-property": 1,
"react/prop-types": 1,
"react/react-in-jsx-scope": 1,
"react/require-extension": 1,
"react/self-closing-comp": 1,
"react/sort-comp": 1,
"react/wrap-multilines": 1
Expand All @@ -79,6 +80,7 @@ Finally, enable all of the rules that you would like to use.
* [no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property
* [prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition
* [react-in-jsx-scope](docs/rules/react-in-jsx-scope.md): Prevent missing React when using JSX
* [require-extension](docs/rules/require-extension.md): Restrict file extensions that may be required
* [self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children
* [sort-comp](docs/rules/sort-comp.md): Enforce component methods order
* [wrap-multilines](docs/rules/wrap-multilines.md): Prevent missing parentheses around multilines JSX
Expand Down
60 changes: 58 additions & 2 deletions docs/rules/display-name.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,62 @@ var Hello = React.createClass({
});
```

## When Not To Use It
## Rule Options

If you are using JSX this value is already automatically set and it is safe for you to disable this rule.
```js
...
"display-name": [<enabled>, { "acceptTranspilerName": <boolean> }]
...
```

### `acceptTranspilerName`

When `true` the rule will accept the name set by the transpiler and does not require a `displayName` property in this case.

The following patterns are considered okay and do not cause warnings:

```js
var Hello = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
module.exports = Hello;
```

```js
export default class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
```

With the following patterns the transpiler can not assign a name for the component and therefore it will still cause warnings:

```js
module.exports = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
```

```js
export default class extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
```

```js
function HelloComponent() {
return React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
}
module.exports = HelloComponent();
```
38 changes: 38 additions & 0 deletions docs/rules/require-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Restrict file extensions that may be required (require-extension)

`require()` statements should generally not include a file extension as there is a well defined mechanism for resolving a module ID to a specific file. This rule inspects the module ID being required and creates a warning if the ID contains a '.jsx' file extension.

Note: this rule does not prevent required files from containing these extensions, it merely prevents the extension from being included in the `require()` statement.

## Rule Details

The following patterns are considered warnings:

```js
var index = require('./index.jsx');

// When [1, {extensions: ['.js']}]
var index = require('./index.js');
```

The following patterns are not considered warnings:

```js
var index = require('./index');

var eslint = require('eslint');
```

## Rule Options

The set of forbidden extensions is configurable. By default '.jsx' is blocked. If you wanted to forbid both '.jsx' and '.js', the configuration would be:

```js
"rules": {
"react/require-extension": [1, { "extensions": [".js", ".jsx"] }],
}
```

## When Not To Use It

If you have file in your project with a '.jsx' file extension and do not have `require()` configured to automatically resolve '.jsx' files.
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ module.exports = {
'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
'jsx-sort-prop-types': require('./lib/rules/jsx-sort-prop-types'),
'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'),
'sort-comp': require('./lib/rules/sort-comp')
'sort-comp': require('./lib/rules/sort-comp'),
'require-extension': require('./lib/rules/require-extension')
},
rulesConfig: {
'jsx-uses-react': 0,
Expand All @@ -37,6 +38,7 @@ module.exports = {
'jsx-sort-props': 0,
'jsx-sort-prop-types': 0,
'jsx-boolean-value': 0,
'sort-comp': 0
'sort-comp': 0,
'require-extension': 0
}
};
58 changes: 57 additions & 1 deletion lib/rules/display-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ var ComponentList = componentUtil.List;

module.exports = function(context) {

var config = context.options[0] || {};
var acceptTranspilerName = config.acceptTranspilerName || false;

var componentList = new ComponentList();

var MISSING_MESSAGE = 'Component definition is missing display name';
Expand Down Expand Up @@ -42,7 +45,10 @@ module.exports = function(context) {
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node.type === 'ClassProperty') {
var tokens = context.getFirstTokens(node, 2);
if (tokens[0].value === 'displayName' || tokens[1].value === 'displayName') {
if (
tokens[0].value === 'displayName' ||
(tokens[1] && tokens[1].value === 'displayName')
) {
return true;
}
return false;
Expand Down Expand Up @@ -77,6 +83,39 @@ module.exports = function(context) {
);
}

/**
* Checks if the component have a name set by the transpiler
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True ifcomponent have a name, false if not.
*/
function hasTranspilerName(node) {
var namedAssignment = (
node.type === 'ObjectExpression' &&
node.parent &&
node.parent.parent &&
node.parent.parent.type === 'AssignmentExpression' && (
!node.parent.parent.left.object ||
node.parent.parent.left.object.name !== 'module' ||
node.parent.parent.left.property.name !== 'exports'
)
);
var namedDeclaration = (
node.type === 'ObjectExpression' &&
node.parent &&
node.parent.parent &&
node.parent.parent.type === 'VariableDeclarator'
);
var namedClass = (
node.type === 'ClassDeclaration' &&
node.id && node.id.name
);

if (namedAssignment || namedDeclaration || namedClass) {
return true;
}
return false;
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
Expand Down Expand Up @@ -109,6 +148,13 @@ module.exports = function(context) {
markDisplayNameAsDeclared(node);
},

ClassDeclaration: function(node) {
if (!acceptTranspilerName || !hasTranspilerName(node)) {
return;
}
markDisplayNameAsDeclared(node);
},

ObjectExpression: function(node) {
// Search for the displayName declaration
node.properties.forEach(function(property) {
Expand All @@ -117,6 +163,16 @@ module.exports = function(context) {
}
markDisplayNameAsDeclared(node);
});
// Has transpiler name
if (acceptTranspilerName && hasTranspilerName(node)) {
markDisplayNameAsDeclared(node);
}

if (componentUtil.isComponentDefinition(node)) {
componentList.set(context, node, {
isReactComponent: true
});
}
},

'Program:exit': function() {
Expand Down
8 changes: 6 additions & 2 deletions lib/rules/jsx-sort-prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,19 @@ module.exports = function(context) {
);
}

function getKey(node) {
return node.key.type === 'Identifier' ? node.key.name : node.key.value;
}

/**
* Checks if propTypes declarations are sorted
* @param {Array} declarations The array of AST nodes being checked.
* @returns {void}
*/
function checkSorted(declarations) {
declarations.reduce(function(prev, curr) {
var prevPropName = prev.key.name;
var currenPropName = curr.key.name;
var prevPropName = getKey(prev);
var currenPropName = getKey(curr);

if (ignoreCase) {
prevPropName = prevPropName.toLowerCase();
Expand Down
Loading

0 comments on commit caf03ce

Please sign in to comment.