eslint-plugin-react
Advanced tools
Comparing version 1.6.1 to 2.0.0
@@ -0,1 +1,21 @@ | ||
2.0.0 / 2015-03-29 | ||
================== | ||
* update dependencies | ||
* add jsx-sort-props rule ([#16][]) | ||
* add no-unknown-property rule ([#28][]) | ||
* add ignore option to prop-types rule | ||
* breaking in prop-types the children prop is no longer ignored | ||
* fix components are now detected when using ES6 classes ([#24][]) | ||
* fix prop-types now return the right line/column ([#33][]) | ||
* fix props are now detected when destructuring ([#27][]) | ||
* fix only check for computed property names in prop-types ([#36][] @burnnat) | ||
[#16]: https://github.com/yannickcr/eslint-plugin-react/issues/16 | ||
[#28]: https://github.com/yannickcr/eslint-plugin-react/issues/28 | ||
[#24]: https://github.com/yannickcr/eslint-plugin-react/issues/24 | ||
[#33]: https://github.com/yannickcr/eslint-plugin-react/issues/33 | ||
[#27]: https://github.com/yannickcr/eslint-plugin-react/issues/27 | ||
[#36]: https://github.com/yannickcr/eslint-plugin-react/pull/36 | ||
1.6.1 / 2015-03-25 | ||
@@ -2,0 +22,0 @@ ================== |
@@ -16,3 +16,5 @@ 'use strict'; | ||
'jsx-no-undef': require('./lib/rules/jsx-no-undef'), | ||
'jsx-quotes': require('./lib/rules/jsx-quotes') | ||
'jsx-quotes': require('./lib/rules/jsx-quotes'), | ||
'no-unknown-property': require('./lib/rules/no-unknown-property'), | ||
'jsx-sort-props': require('./lib/rules/jsx-sort-props') | ||
}, | ||
@@ -31,4 +33,6 @@ rulesConfig: { | ||
'jsx-no-undef': 0, | ||
'jsx-quotes': 0 | ||
'jsx-quotes': 0, | ||
'no-unknown-property': 0, | ||
'jsx-sort-props': 0 | ||
} | ||
}; |
@@ -7,2 +7,5 @@ /** | ||
var componentUtil = require('../util/component'); | ||
var ComponentList = componentUtil.List; | ||
// ------------------------------------------------------------------------------ | ||
@@ -14,15 +17,45 @@ // Rule Definition | ||
var hasDisplayName = false; | ||
var componentList = new ComponentList(); | ||
function isComponentDefinition(node) { | ||
return ( | ||
var MISSING_MESSAGE = 'Component definition is missing display name'; | ||
var MISSING_MESSAGE_NAMED_COMP = '{{component}} component definition is missing display name'; | ||
/** | ||
* Checks if we are declaring a display name | ||
* @param {ASTNode} node The AST node being checked. | ||
* @returns {Boolean} True if we are declaring a display name, false if not. | ||
*/ | ||
function isDisplayNameDeclaration(node) { | ||
return Boolean( | ||
node && | ||
node.callee && | ||
node.callee.object && | ||
node.callee.property && | ||
node.callee.object.name === 'React' && | ||
node.callee.property.name === 'createClass' | ||
node.name === 'displayName' | ||
); | ||
} | ||
/** | ||
* Mark a prop type as declared | ||
* @param {ASTNode} node The AST node being checked. | ||
*/ | ||
function markDisplayNameAsDeclared(node) { | ||
componentList.set(context, node, { | ||
hasDisplayName: true | ||
}); | ||
} | ||
/** | ||
* Reports missing display name for a given component | ||
* @param {Object} component The component to process | ||
*/ | ||
function reportMissingDisplayName(component) { | ||
if (!component || component.hasDisplayName === true) { | ||
return; | ||
} | ||
context.report( | ||
component.node, | ||
component.name === componentUtil.DEFAULT_COMPONENT_NAME ? MISSING_MESSAGE : MISSING_MESSAGE_NAMED_COMP, { | ||
component: component.name | ||
} | ||
); | ||
} | ||
// -------------------------------------------------------------------------- | ||
@@ -34,27 +67,39 @@ // Public | ||
ObjectExpression: function(node) { | ||
if (!isComponentDefinition(node.parent)) { | ||
MemberExpression: function(node) { | ||
if (!isDisplayNameDeclaration(node.property)) { | ||
return; | ||
} | ||
var component = componentList.getByName(node.object.name); | ||
if (!component) { | ||
return; | ||
} | ||
markDisplayNameAsDeclared(component.node); | ||
}, | ||
ObjectExpression: function(node) { | ||
// Search for the displayName declaration | ||
node.properties.forEach(function(property) { | ||
var keyName = property.key.name || property.key.value; | ||
if (keyName === 'displayName') { | ||
hasDisplayName = true; | ||
if (!isDisplayNameDeclaration(property.key)) { | ||
return; | ||
} | ||
markDisplayNameAsDeclared(node); | ||
}); | ||
}, | ||
'ObjectExpression:exit': function(node) { | ||
'Program:exit': function() { | ||
var list = componentList.getList(); | ||
// Report missing display name for all classes | ||
for (var component in list) { | ||
if (!list.hasOwnProperty(component)) { | ||
continue; | ||
} | ||
reportMissingDisplayName(list[component]); | ||
} | ||
}, | ||
if (!isComponentDefinition(node.parent)) { | ||
ReturnStatement: function(node) { | ||
if (!componentUtil.isReactComponent(context, node)) { | ||
return; | ||
} | ||
if (!hasDisplayName) { | ||
context.report(node, 'Component definition is missing display name'); | ||
} | ||
hasDisplayName = false; | ||
componentList.set(context, node); | ||
} | ||
@@ -61,0 +106,0 @@ }; |
@@ -7,2 +7,5 @@ /** | ||
var componentUtil = require('../util/component'); | ||
var ComponentList = componentUtil.List; | ||
// ------------------------------------------------------------------------------ | ||
@@ -14,4 +17,6 @@ // Rule Definition | ||
var componentCounter = 0; | ||
var componentList = new ComponentList(); | ||
var MULTI_COMP_MESSAGE = 'Declare only one React component per file'; | ||
// -------------------------------------------------------------------------- | ||
@@ -22,8 +27,25 @@ // Public | ||
return { | ||
MemberExpression: function(node) { | ||
if (node.object.name === 'React' && node.property.name === 'createClass' && ++componentCounter > 1) { | ||
context.report(node, 'Declare only one React component per file'); | ||
'Program:exit': function() { | ||
if (componentList.count() <= 1) { | ||
return; | ||
} | ||
var list = componentList.getList(); | ||
var i = 0; | ||
for (var component in list) { | ||
if (!list.hasOwnProperty(component) || ++i === 1) { | ||
continue; | ||
} | ||
context.report(list[component].node, MULTI_COMP_MESSAGE); | ||
} | ||
}, | ||
ReturnStatement: function(node) { | ||
if (!componentUtil.isReactComponent(context, node)) { | ||
return; | ||
} | ||
componentList.set(context, node); | ||
} | ||
}; | ||
}; |
@@ -7,2 +7,5 @@ /** | ||
var componentUtil = require('../util/component'); | ||
var ComponentList = componentUtil.List; | ||
// ------------------------------------------------------------------------------ | ||
@@ -14,17 +17,153 @@ // Rule Definition | ||
var declaredPropTypes = []; | ||
var usedPropTypes = []; | ||
var ignorePropsValidation = false; | ||
var configuration = context.options[0] || {}; | ||
var ignored = configuration.ignore || []; | ||
function isComponentDefinition(node) { | ||
return ( | ||
var componentList = new ComponentList(); | ||
var MISSING_MESSAGE = '\'{{name}}\' is missing in props validation'; | ||
var MISSING_MESSAGE_NAMED_COMP = '\'{{name}}\' is missing in props validation for {{component}}'; | ||
/** | ||
* Checks if we are using a prop | ||
* @param {ASTNode} node The AST node being checked. | ||
* @returns {Boolean} True if we are using a prop, false if not. | ||
*/ | ||
function isPropTypesUsage(node) { | ||
return Boolean( | ||
node.object.type === 'ThisExpression' && | ||
node.property.name === 'props' | ||
); | ||
} | ||
/** | ||
* Checks if we are declaring a prop | ||
* @param {ASTNode} node The AST node being checked. | ||
* @returns {Boolean} True if we are declaring a prop, false if not. | ||
*/ | ||
function isPropTypesDeclaration(node) { | ||
return Boolean( | ||
node && | ||
node.callee && | ||
node.callee.object && | ||
node.callee.property && | ||
node.callee.object.name === 'React' && | ||
node.callee.property.name === 'createClass' | ||
node.name === 'propTypes' | ||
); | ||
} | ||
/** | ||
* Checks if the prop is ignored | ||
* @param {String} name Name of the prop to check. | ||
* @returns {Boolean} True if the prop is ignored, false if not. | ||
*/ | ||
function isIgnored(name) { | ||
return ignored.indexOf(name) !== -1; | ||
} | ||
/** | ||
* Checks if the prop is declared | ||
* @param {String} name Name of the prop to check. | ||
* @param {Object} component The component to process | ||
* @returns {Boolean} True if the prop is declared, false if not. | ||
*/ | ||
function isDeclaredInComponent(component, name) { | ||
return ( | ||
component.declaredPropTypes && | ||
component.declaredPropTypes.indexOf(name) !== -1 | ||
); | ||
} | ||
/** | ||
* Mark a prop type as used | ||
* @param {ASTNode} node The AST node being marked. | ||
*/ | ||
function markPropTypesAsUsed(node) { | ||
var component = componentList.getByNode(context, node); | ||
var usedPropTypes = component && component.usedPropTypes || []; | ||
var type; | ||
if (node.parent.property && node.parent.property.name && !node.parent.computed) { | ||
type = 'direct'; | ||
} else if ( | ||
node.parent.parent.declarations && | ||
node.parent.parent.declarations[0].id.properties && | ||
node.parent.parent.declarations[0].id.properties[0].key.name | ||
) { | ||
type = 'destructuring'; | ||
} | ||
switch (type) { | ||
case 'direct': | ||
usedPropTypes.push({ | ||
name: node.parent.property.name, | ||
node: node | ||
}); | ||
break; | ||
case 'destructuring': | ||
for (var i = 0, j = node.parent.parent.declarations[0].id.properties.length; i < j; i++) { | ||
usedPropTypes.push({ | ||
name: node.parent.parent.declarations[0].id.properties[i].key.name, | ||
node: node | ||
}); | ||
} | ||
break; | ||
default: | ||
break; | ||
} | ||
componentList.set(context, node, { | ||
usedPropTypes: usedPropTypes | ||
}); | ||
} | ||
/** | ||
* Mark a prop type as declared | ||
* @param {ASTNode} node The AST node being checked. | ||
* @param {propTypes} node The AST node containing the proptypes | ||
*/ | ||
function markPropTypesAsDeclared(node, propTypes) { | ||
var component = componentList.getByNode(context, node); | ||
var declaredPropTypes = component && component.declaredPropTypes || []; | ||
var ignorePropsValidation = false; | ||
switch (propTypes.type) { | ||
case 'ObjectExpression': | ||
for (var i = 0, j = propTypes.properties.length; i < j; i++) { | ||
declaredPropTypes.push(propTypes.properties[i].key.name); | ||
} | ||
break; | ||
case 'MemberExpression': | ||
declaredPropTypes.push(propTypes.property.name); | ||
break; | ||
default: | ||
ignorePropsValidation = true; | ||
break; | ||
} | ||
componentList.set(context, node, { | ||
declaredPropTypes: declaredPropTypes, | ||
ignorePropsValidation: ignorePropsValidation | ||
}); | ||
} | ||
/** | ||
* Reports undeclared proptypes for a given component | ||
* @param {Object} component The component to process | ||
*/ | ||
function reportUndeclaredPropTypes(component) { | ||
if (!component || !component.usedPropTypes || component.ignorePropsValidation === true) { | ||
return; | ||
} | ||
var name; | ||
for (var i = 0, j = component.usedPropTypes.length; i < j; i++) { | ||
name = component.usedPropTypes[i].name; | ||
if (isDeclaredInComponent(component, name) || isIgnored(name)) { | ||
continue; | ||
} | ||
context.report( | ||
component.usedPropTypes[i].node, | ||
component.name === componentUtil.DEFAULT_COMPONENT_NAME ? MISSING_MESSAGE : MISSING_MESSAGE_NAMED_COMP, { | ||
name: name, | ||
component: component.name | ||
} | ||
); | ||
} | ||
} | ||
// -------------------------------------------------------------------------- | ||
@@ -37,46 +176,51 @@ // Public | ||
MemberExpression: function(node) { | ||
if (node.object.type !== 'ThisExpression' || node.property.name !== 'props' || !node.parent.property) { | ||
return; | ||
var type; | ||
if (isPropTypesUsage(node)) { | ||
type = 'usage'; | ||
} else if (isPropTypesDeclaration(node.property)) { | ||
type = 'declaration'; | ||
} | ||
usedPropTypes.push(node.parent.property.name); | ||
switch (type) { | ||
case 'usage': | ||
markPropTypesAsUsed(node); | ||
break; | ||
case 'declaration': | ||
var component = componentList.getByName(node.object.name); | ||
if (!component) { | ||
return; | ||
} | ||
markPropTypesAsDeclared(component.node, node.parent.right || node.parent); | ||
break; | ||
default: | ||
break; | ||
} | ||
}, | ||
ObjectExpression: function(node) { | ||
if (!isComponentDefinition(node.parent)) { | ||
return; | ||
} | ||
// Search for the displayName declaration | ||
node.properties.forEach(function(property) { | ||
var keyName = property.key.name || property.key.value; | ||
if (keyName !== 'propTypes') { | ||
if (!isPropTypesDeclaration(property.key)) { | ||
return; | ||
} | ||
if (property.value.type !== 'ObjectExpression') { | ||
ignorePropsValidation = true; | ||
return; | ||
} | ||
for (var i = 0, j = property.value.properties.length; i < j; i++) { | ||
declaredPropTypes.push(property.value.properties[i].key.name); | ||
} | ||
markPropTypesAsDeclared(node, property.value); | ||
}); | ||
}, | ||
'ObjectExpression:exit': function(node) { | ||
if (!isComponentDefinition(node.parent)) { | ||
return; | ||
} | ||
for (var i = 0, j = usedPropTypes.length; !ignorePropsValidation && i < j; i++) { | ||
if (declaredPropTypes.indexOf(usedPropTypes[i]) !== -1 || usedPropTypes[i] === 'children') { | ||
'Program:exit': function() { | ||
var list = componentList.getList(); | ||
// Report undeclared proptypes for all classes | ||
for (var component in list) { | ||
if (!list.hasOwnProperty(component)) { | ||
continue; | ||
} | ||
context.report(node, '\'' + usedPropTypes[i] + '\' is missing in props validation'); | ||
reportUndeclaredPropTypes(list[component]); | ||
} | ||
}, | ||
declaredPropTypes.length = 0; | ||
usedPropTypes.length = 0; | ||
ignorePropsValidation = false; | ||
ReturnStatement: function(node) { | ||
if (!componentUtil.isReactComponent(context, node)) { | ||
return; | ||
} | ||
componentList.set(context, node); | ||
} | ||
@@ -83,0 +227,0 @@ }; |
{ | ||
"name": "eslint-plugin-react", | ||
"version": "1.6.1", | ||
"version": "2.0.0", | ||
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>", | ||
@@ -27,5 +27,5 @@ "description": "React specific linting rules for ESLint", | ||
"coveralls": "2.11.2", | ||
"eslint": "0.17.1", | ||
"eslint": "0.18", | ||
"eslint-tester": "0.6.0", | ||
"istanbul": "0.3.8", | ||
"istanbul": "0.3.11", | ||
"mocha": "2.2.1" | ||
@@ -32,0 +32,0 @@ }, |
@@ -48,2 +48,3 @@ ESLint-plugin-React | ||
"react/jsx-no-undef": 1, | ||
"react/jsx-sort-props": 1, | ||
"react/jsx-uses-react": 1, | ||
@@ -54,2 +55,3 @@ "react/jsx-uses-vars": 1, | ||
"react/no-multi-comp": 1, | ||
"react/no-unknown-property": 1, | ||
"react/prop-types": 1, | ||
@@ -68,2 +70,3 @@ "react/react-in-jsx-scope": 1, | ||
* [jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX | ||
* [jsx-sort-props](docs/rules/jsx-sort-props.md): Enforce props alphabetical sorting | ||
* [jsx-uses-react](docs/rules/jsx-uses-react.md): Prevent React to be incorrectly marked as unused | ||
@@ -74,2 +77,3 @@ * [jsx-uses-vars](docs/rules/jsx-uses-vars.md): Prevent variables used in JSX to be incorrectly marked as unused | ||
* [no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file | ||
* [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 | ||
@@ -76,0 +80,0 @@ * [react-in-jsx-scope](docs/rules/react-in-jsx-scope.md): Prevent missing React when using JSX |
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
42787
20
980
113