eslint-plugin-react
Advanced tools
Comparing version 4.2.1 to 4.2.2
@@ -6,2 +6,21 @@ # Change Log | ||
## [4.2.2] - 2016-03-14 | ||
### Fixed | ||
* Rewrite `prefer-stateless-function` rule ([#491][]) | ||
* Fix `self-closing-comp` to treat non-breaking spaces as content ([#496][]) | ||
* Fix detection for direct props in `prop-types` ([#497][]) | ||
* Fix annotated function detection in `prop-types` ([#498][]) | ||
* Fix `this` usage in `jsx-no-undef` ([#489][]) | ||
### Changed | ||
* Update dependencies | ||
* Add shared setting for React version | ||
[4.2.2]: https://github.com/yannickcr/eslint-plugin-react/compare/v4.2.1...v4.2.2 | ||
[#491]: https://github.com/yannickcr/eslint-plugin-react/issues/491 | ||
[#496]: https://github.com/yannickcr/eslint-plugin-react/issues/496 | ||
[#497]: https://github.com/yannickcr/eslint-plugin-react/issues/497 | ||
[#498]: https://github.com/yannickcr/eslint-plugin-react/issues/498 | ||
[#489]: https://github.com/yannickcr/eslint-plugin-react/issues/489 | ||
## [4.2.1] - 2016-03-08 | ||
@@ -8,0 +27,0 @@ ### Fixed |
@@ -35,2 +35,7 @@ /** | ||
// Ignore 'this' keyword (also maked as JSXIdentifier when used in JSX) | ||
if (node.name === 'this') { | ||
return; | ||
} | ||
while (scope.type !== 'global') { | ||
@@ -37,0 +42,0 @@ scope = scope.upper; |
@@ -9,2 +9,3 @@ /** | ||
var pragmaUtil = require('../util/pragma'); | ||
var versionUtil = require('../util/version'); | ||
@@ -24,11 +25,2 @@ // ------------------------------------------------------------------------------ | ||
var sourceCode = context.getSourceCode(); | ||
// Validate React version passed in options | ||
// (append the patch version if missing, allowing shorthands like 0.12 for React 0.12.0) | ||
var optVer = context.options[0] ? context.options[0].react : '999.999.999'; | ||
optVer = /^[0-9]+\.[0-9]+$/.test(optVer) ? optVer + '.0' : optVer; | ||
optVer = optVer.split('.').map(function(part) { | ||
return Number(part); | ||
}); | ||
var pragma = pragmaUtil.getFromContext(context); | ||
@@ -65,13 +57,2 @@ | ||
function checkVersion(methodVer) { | ||
methodVer = methodVer.split('.').map(function(part) { | ||
return Number(part); | ||
}); | ||
var higherMajor = methodVer[0] < optVer[0]; | ||
var higherMinor = methodVer[0] === optVer[0] && methodVer[1] < optVer[1]; | ||
var higherOrEqualPatch = methodVer[0] === optVer[0] && methodVer[1] === optVer[1] && methodVer[2] <= optVer[2]; | ||
return higherMajor || higherMinor || higherOrEqualPatch; | ||
} | ||
function isDeprecated(type, method) { | ||
@@ -83,3 +64,3 @@ var deprecated = getDeprecated(); | ||
deprecated[type][method] && | ||
checkVersion(deprecated[type][method][0]) | ||
versionUtil.test(context, deprecated[type][method][0]) | ||
); | ||
@@ -86,0 +67,0 @@ } |
/** | ||
* @fileoverview Enforce stateless components to be written as a pure function | ||
* @author Yannick Croissant | ||
* @author Alberto Rodríguez | ||
* @copyright 2015 Alberto Rodríguez. All rights reserved. | ||
*/ | ||
@@ -17,15 +19,2 @@ 'use strict'; | ||
var lifecycleMethods = [ | ||
'state', | ||
'getInitialState', | ||
'getChildContext', | ||
'componentWillMount', | ||
'componentDidMount', | ||
'componentWillReceiveProps', | ||
'shouldComponentUpdate', | ||
'componentWillUpdate', | ||
'componentDidUpdate', | ||
'componentWillUnmount' | ||
]; | ||
// -------------------------------------------------------------------------- | ||
@@ -68,10 +57,73 @@ // Public | ||
/** | ||
* Check if a given AST node have any lifecycle method | ||
* Checks whether the constructor body is a redundant super call. | ||
* @see ESLint no-useless-constructor rule | ||
* @param {Array} body - constructor body content. | ||
* @param {Array} ctorParams - The params to check against super call. | ||
* @returns {boolean} true if the construtor body is redundant | ||
*/ | ||
function isRedundantSuperCall(body, ctorParams) { | ||
if ( | ||
body.length !== 1 || | ||
body[0].type !== 'ExpressionStatement' || | ||
body[0].expression.callee.type !== 'Super' | ||
) { | ||
return false; | ||
} | ||
var superArgs = body[0].expression.arguments; | ||
var firstSuperArg = superArgs[0]; | ||
var lastSuperArgIndex = superArgs.length - 1; | ||
var lastSuperArg = superArgs[lastSuperArgIndex]; | ||
var isSimpleParameterList = ctorParams.every(function(param) { | ||
return param.type === 'Identifier' || param.type === 'RestElement'; | ||
}); | ||
/** | ||
* Checks if a super argument is the same with constructor argument | ||
* @param {ASTNode} arg argument node | ||
* @param {number} index argument index | ||
* @returns {boolean} true if the arguments are same, false otherwise | ||
*/ | ||
function isSameIdentifier(arg, index) { | ||
return ( | ||
arg.type === 'Identifier' && | ||
arg.name === ctorParams[index].name | ||
); | ||
} | ||
var spreadsArguments = | ||
superArgs.length === 1 && | ||
firstSuperArg.type === 'SpreadElement' && | ||
firstSuperArg.argument.name === 'arguments'; | ||
var passesParamsAsArgs = | ||
superArgs.length === ctorParams.length && | ||
superArgs.every(isSameIdentifier) || | ||
superArgs.length <= ctorParams.length && | ||
superArgs.slice(0, -1).every(isSameIdentifier) && | ||
lastSuperArg.type === 'SpreadElement' && | ||
ctorParams[lastSuperArgIndex].type === 'RestElement' && | ||
lastSuperArg.argument.name === ctorParams[lastSuperArgIndex].argument.name; | ||
return isSimpleParameterList && (spreadsArguments || passesParamsAsArgs); | ||
} | ||
/** | ||
* Check if a given AST node have any other properties the ones available in stateless components | ||
* @param {ASTNode} node The AST node being checked. | ||
* @returns {Boolean} True if the node has at least one lifecycle method, false if not. | ||
* @returns {Boolean} True if the node has at least one other property, false if not. | ||
*/ | ||
function hasLifecycleMethod(node) { | ||
function hasOtherProperties(node) { | ||
var properties = getComponentProperties(node); | ||
return properties.some(function(property) { | ||
return lifecycleMethods.indexOf(getPropertyName(property)) !== -1; | ||
var name = getPropertyName(property); | ||
var isDisplayName = name === 'displayName'; | ||
var isPropTypes = name === 'propTypes' || name === 'props' && property.typeAnnotation; | ||
var contextTypes = name === 'contextTypes'; | ||
var isUselessConstructor = | ||
property.kind === 'constructor' && | ||
isRedundantSuperCall(property.value.body.body, property.value.params) | ||
; | ||
var isRender = name === 'render'; | ||
return !isDisplayName && !isPropTypes && !contextTypes && !isUselessConstructor && !isRender; | ||
}); | ||
@@ -84,5 +136,5 @@ } | ||
*/ | ||
function markSetStateAsUsed(node) { | ||
function markThisAsUsed(node) { | ||
components.set(node, { | ||
useSetState: true | ||
useThis: true | ||
}); | ||
@@ -101,14 +153,44 @@ } | ||
/** | ||
* Mark return as invalid | ||
* @param {ASTNode} node The AST node being checked. | ||
*/ | ||
function markReturnAsInvalid(node) { | ||
components.set(node, { | ||
invalidReturn: true | ||
}); | ||
} | ||
return { | ||
CallExpression: function(node) { | ||
var callee = node.callee; | ||
if (callee.type !== 'MemberExpression') { | ||
// Mark `this` destructuring as a usage of `this` | ||
VariableDeclarator: function(node) { | ||
// Ignore destructuring on other than `this` | ||
if (!node.id || node.id.type !== 'ObjectPattern' || !node.init || node.init.type !== 'ThisExpression') { | ||
return; | ||
} | ||
if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'setState') { | ||
// Ignore `props` and `context` | ||
var useThis = node.id.properties.some(function(property) { | ||
var name = getPropertyName(property); | ||
return name !== 'props' && name !== 'context'; | ||
}); | ||
if (!useThis) { | ||
return; | ||
} | ||
markSetStateAsUsed(node); | ||
markThisAsUsed(node); | ||
}, | ||
// Mark `this` usage | ||
MemberExpression: function(node) { | ||
// Ignore calls to `this.props` and `this.context` | ||
if ( | ||
node.object.type !== 'ThisExpression' || | ||
(node.property.name || node.property.value) === 'props' || | ||
(node.property.name || node.property.value) === 'context' | ||
) { | ||
return; | ||
} | ||
markThisAsUsed(node); | ||
}, | ||
// Mark `ref` usage | ||
JSXAttribute: function(node) { | ||
@@ -122,2 +204,19 @@ var name = sourceCode.getText(node.name); | ||
// Mark `render` that do not return some JSX | ||
ReturnStatement: function(node) { | ||
var blockNode; | ||
var scope = context.getScope(); | ||
while (scope) { | ||
blockNode = scope.block && scope.block.parent; | ||
if (blockNode && (blockNode.type === 'MethodDefinition' || blockNode.type === 'Property')) { | ||
break; | ||
} | ||
scope = scope.upper; | ||
} | ||
if (!blockNode || !blockNode.key || blockNode.key.name !== 'render' || utils.isReturningJSX(node)) { | ||
return; | ||
} | ||
markReturnAsInvalid(node); | ||
}, | ||
'Program:exit': function() { | ||
@@ -128,5 +227,6 @@ var list = components.list(); | ||
!list.hasOwnProperty(component) || | ||
hasLifecycleMethod(list[component].node) || | ||
list[component].useSetState || | ||
hasOtherProperties(list[component].node) || | ||
list[component].useThis || | ||
list[component].useRef || | ||
list[component].invalidReturn || | ||
(!utils.isES5Component(list[component].node) && !utils.isES6Component(list[component].node)) | ||
@@ -133,0 +233,0 @@ ) { |
@@ -14,2 +14,8 @@ /** | ||
// ------------------------------------------------------------------------------ | ||
// Constants | ||
// ------------------------------------------------------------------------------ | ||
var DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/; | ||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
@@ -88,8 +94,6 @@ // ------------------------------------------------------------------------------ | ||
var tokens = context.getFirstTokens(node.params[0], 2); | ||
if ( | ||
node.params[0].typeAnnotation && ( | ||
tokens[0].value === 'props' || | ||
(tokens[1] && tokens[1].value === 'props') | ||
) | ||
) { | ||
var isAnnotated = node.params[0].typeAnnotation; | ||
var isDestructuredProps = node.params[0].type === 'ObjectPattern'; | ||
var isProps = tokens[0].value === 'props' || (tokens[1] && tokens[1].value === 'props'); | ||
if (isAnnotated && (isDestructuredProps || isProps)) { | ||
return true; | ||
@@ -466,3 +470,3 @@ } | ||
function getPropertyName(node) { | ||
var isDirectProp = /^props(\.|\[)/.test(sourceCode.getText(node)); | ||
var isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node)); | ||
var isInClassComponent = utils.getParentES6Component() || utils.getParentES5Component(); | ||
@@ -573,3 +577,3 @@ var isNotInConstructor = !inConstructor(node); | ||
var isDirectProp = /^props(\.|\[)/.test(sourceCode.getText(node)); | ||
var isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node)); | ||
@@ -576,0 +580,0 @@ usedPropTypes.push({ |
@@ -26,3 +26,3 @@ /** | ||
!childrens.length || | ||
(childrens.length === 1 && childrens[0].type === 'Literal' && !childrens[0].value.trim()) | ||
(childrens.length === 1 && childrens[0].type === 'Literal' && !childrens[0].value.replace(/(?!\xA0)\s/g, '')) | ||
) { | ||
@@ -29,0 +29,0 @@ return false; |
{ | ||
"name": "eslint-plugin-react", | ||
"version": "4.2.1", | ||
"version": "4.2.2", | ||
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>", | ||
@@ -26,5 +26,5 @@ "description": "React specific linting rules for ESLint", | ||
"devDependencies": { | ||
"babel-eslint": "6.0.0-beta.1", | ||
"babel-eslint": "6.0.0-beta.6", | ||
"coveralls": "2.11.8", | ||
"eslint": "2.2.0", | ||
"eslint": "2.4.0", | ||
"istanbul": "0.4.2", | ||
@@ -31,0 +31,0 @@ "mocha": "2.4.5" |
@@ -40,3 +40,4 @@ ESLint-plugin-React | ||
"react": { | ||
"pragma": "React" // Pragma to use, default to "React" | ||
"pragma": "React", // Pragma to use, default to "React" | ||
"version": "0.14.0" // React version, default to the latest React stable release | ||
} | ||
@@ -43,0 +44,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
213987
49
4864
185