Socket
Socket
Sign inDemoInstall

eslint-plugin-react

Package Overview
Dependencies
Maintainers
1
Versions
208
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-react - npm Package Compare versions

Comparing version 3.5.1 to 3.6.0

lib/rules/prefer-es6-class.js

28

CHANGELOG.md

@@ -6,2 +6,30 @@ # Change Log

## [3.6.0] - 2015-10-18
### Added
* Add support for stateless function components to `display-name` and `prop-types` ([#237][])
* Add callbacksLast option to `jsx-sort-props` and `jsx-sort-prop-types` ([#242][] @Daniel15)
* Add `prefer-es6-class` rule ([#247][] @hamiltondanielb)
### Fixed
* Fix `forbid-prop-types` crash with destructured PropTypes ([#230][] @epmatsw)
* Fix `forbid-prop-types` to do not modify AST directly ([#249][] @rhysd)
* Fix `prop-types` crash with empty destructuring ([#251][])
* Fix `prop-types` to not validate computed keys in destructuring ([#236][])
### Changed
* Update dependencies
* Improve components detection ([#233][])
* Documentation improvements ([#248][] @dguo)
[3.6.0]: https://github.com/yannickcr/eslint-plugin-react/compare/v3.5.1...v3.6.0
[#237]: https://github.com/yannickcr/eslint-plugin-react/issues/237
[#242]: https://github.com/yannickcr/eslint-plugin-react/pull/242
[#247]: https://github.com/yannickcr/eslint-plugin-react/issues/247
[#230]: https://github.com/yannickcr/eslint-plugin-react/issues/230
[#249]: https://github.com/yannickcr/eslint-plugin-react/issues/249
[#251]: https://github.com/yannickcr/eslint-plugin-react/issues/251
[#236]: https://github.com/yannickcr/eslint-plugin-react/issues/236
[#233]: https://github.com/yannickcr/eslint-plugin-react/issues/233
[#248]: https://github.com/yannickcr/eslint-plugin-react/pull/248
## [3.5.1] - 2015-10-01

@@ -8,0 +36,0 @@ ### Fixed

6

index.js

@@ -32,3 +32,4 @@ 'use strict';

'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
'forbid-prop-types': require('./lib/rules/forbid-prop-types')
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
'prefer-es6-class': require('./lib/rules/prefer-es6-class')
},

@@ -63,4 +64,5 @@ rulesConfig: {

'no-direct-mutation-state': 0,
'forbid-prop-types': 0
'forbid-prop-types': 0,
'prefer-es6-class': 0
}
};

@@ -25,15 +25,2 @@ /**

/**
* Checks if the component must be validated
* @param {Object} component The component to process
* @returns {Boolean} True if the component must be validated, false if not.
*/
function mustBeValidated(component) {
return (
component &&
component.isReactComponent &&
!component.hasDisplayName
);
}
/**
* Checks if we are declaring a display name

@@ -44,3 +31,2 @@ * @param {ASTNode} node The AST node being checked.

function isDisplayNameDeclaration(node) {
// Special case for class properties

@@ -131,3 +117,2 @@ // (babel-eslint does not expose property name so we have to rely on tokens)

}
markDisplayNameAsDeclared(node);

@@ -155,2 +140,3 @@ },

ClassDeclaration: function(node) {
componentList.set(context, node);
if (!acceptTranspilerName || !hasTranspilerName(node)) {

@@ -163,30 +149,25 @@ return;

ObjectExpression: function(node) {
// Search for the displayName declaration
node.properties.forEach(function(property) {
if (!property.key) {
return;
}
if (!isDisplayNameDeclaration(property.key)) {
return;
}
markDisplayNameAsDeclared(node);
});
// Has transpiler name
if (acceptTranspilerName && hasTranspilerName(node)) {
markDisplayNameAsDeclared(node);
}
if (componentUtil.isComponentDefinition(node)) {
componentList.set(context, node, {
isReactComponent: true
componentList.set(context, node);
if (!acceptTranspilerName || !hasTranspilerName(node)) {
// Search for the displayName declaration
node.properties.forEach(function(property) {
if (!property.key || !isDisplayNameDeclaration(property.key)) {
return;
}
markDisplayNameAsDeclared(node);
});
return;
}
markDisplayNameAsDeclared(node);
},
ReturnStatement: function(node) {
componentList.set(context, node);
},
'Program:exit': function() {
var list = componentList.getList();
// Report missing display name for all classes
// Report missing display name for all components
for (var component in list) {
if (!list.hasOwnProperty(component) || !mustBeValidated(list[component])) {
if (!list.hasOwnProperty(component) || list[component].hasDisplayName) {
continue;

@@ -196,14 +177,4 @@ }

}
},
ReturnStatement: function(node) {
if (!componentUtil.isReactComponent(context, node)) {
return;
}
componentList.set(context, node, {
isReactComponent: true
});
}
};
};

@@ -210,0 +181,0 @@

@@ -56,20 +56,26 @@ /**

declarations.forEach(function(declaration) {
var target;
var value = declaration.value;
if (
declaration.value.type === 'MemberExpression' &&
declaration.value.property &&
declaration.value.property.name &&
declaration.value.property.name === 'isRequired'
value.type === 'MemberExpression' &&
value.property &&
value.property.name &&
value.property.name === 'isRequired'
) {
declaration.value = declaration.value.object;
value = value.object;
}
if (
declaration.value.type === 'CallExpression' &&
declaration.value.callee.type === 'MemberExpression'
value.type === 'CallExpression' &&
value.callee.type === 'MemberExpression'
) {
declaration.value = declaration.value.callee;
value = value.callee;
}
if (isForbidden(declaration.value.property.name)) {
context.report(declaration, 'Prop type `' + declaration.value.property.name + '` is forbidden');
if (value.property) {
target = value.property.name;
} else if (value.type === 'Identifier') {
target = value.name;
}
if (isForbidden(target)) {
context.report(declaration, 'Prop type `' + target + '` is forbidden');
}
});

@@ -76,0 +82,0 @@ }

@@ -13,2 +13,3 @@ /**

var configuration = context.options[0] || {};
var callbacksLast = configuration.callbacksLast || false;
var ignoreCase = configuration.ignoreCase || false;

@@ -43,2 +44,6 @@

function isCallbackPropName(propName) {
return /^on[A-Z]/.test(propName);
}
/**

@@ -52,10 +57,24 @@ * Checks if propTypes declarations are sorted

var prevPropName = getKey(prev);
var currenPropName = getKey(curr);
var currentPropName = getKey(curr);
var previousIsCallback = isCallbackPropName(prevPropName);
var currentIsCallback = isCallbackPropName(currentPropName);
if (ignoreCase) {
prevPropName = prevPropName.toLowerCase();
currenPropName = currenPropName.toLowerCase();
currentPropName = currentPropName.toLowerCase();
}
if (currenPropName < prevPropName) {
if (callbacksLast) {
if (!previousIsCallback && currentIsCallback) {
// Entering the callback prop section
return curr;
}
if (previousIsCallback && !currentIsCallback) {
// Encountered a non-callback prop after a callback prop
context.report(prev, 'Callback prop types must be listed after all other prop types');
return prev;
}
}
if (currentPropName < prevPropName) {
context.report(curr, 'Prop types declarations should be sorted alphabetically');

@@ -106,2 +125,5 @@ return prev;

properties: {
callbacksLast: {
type: 'boolean'
},
ignoreCase: {

@@ -108,0 +130,0 @@ type: 'boolean'

@@ -11,2 +11,6 @@ /**

function isCallbackPropName(propName) {
return /^on[A-Z]/.test(propName);
}
module.exports = function(context) {

@@ -16,2 +20,3 @@

var ignoreCase = configuration.ignoreCase || false;
var callbacksLast = configuration.callbacksLast || false;

@@ -25,11 +30,25 @@ return {

var lastPropName = memo.name.name;
var currenPropName = decl.name.name;
var previousPropName = memo.name.name;
var currentPropName = decl.name.name;
var previousIsCallback = isCallbackPropName(previousPropName);
var currentIsCallback = isCallbackPropName(currentPropName);
if (ignoreCase) {
lastPropName = lastPropName.toLowerCase();
currenPropName = currenPropName.toLowerCase();
previousPropName = previousPropName.toLowerCase();
currentPropName = currentPropName.toLowerCase();
}
if (currenPropName < lastPropName) {
if (callbacksLast) {
if (!previousIsCallback && currentIsCallback) {
// Entering the callback prop section
return decl;
}
if (previousIsCallback && !currentIsCallback) {
// Encountered a non-callback prop after a callback prop
context.report(memo, 'Callbacks must be listed after all other props');
return memo;
}
}
if (currentPropName < previousPropName) {
context.report(decl, 'Props should be sorted alphabetically');

@@ -48,2 +67,7 @@ return memo;

properties: {
// Whether callbacks (prefixed with "on") should be listed at the very end,
// after all other props.
callbacksLast: {
type: 'boolean'
},
ignoreCase: {

@@ -50,0 +74,0 @@ type: 'boolean'

@@ -24,6 +24,3 @@ /**

function isValid(component) {
var isNotReactComponent = Boolean(component && !component.isReactComponent);
var doNotMutateSetState = Boolean(component && !component.mutateSetState);
return isNotReactComponent || doNotMutateSetState;
return Boolean(component && !component.mutateSetState);
}

@@ -49,2 +46,10 @@

ObjectExpression: function(node) {
componentList.set(context, node);
},
ClassDeclaration: function(node) {
componentList.set(context, node);
},
AssignmentExpression: function(node) {

@@ -81,11 +86,2 @@ var item;

}
},
ReturnStatement: function(node) {
if (!componentUtil.isReactComponent(context, node)) {
return;
}
componentList.set(context, node, {
isReactComponent: true
});
}

@@ -92,0 +88,0 @@ };

@@ -25,2 +25,11 @@ /**

return {
ClassDeclaration: function(node) {
componentList.set(context, node);
},
ObjectExpression: function(node) {
componentList.set(context, node);
},
'Program:exit': function() {

@@ -40,9 +49,2 @@ if (componentList.count() <= 1) {

}
},
ReturnStatement: function(node) {
if (!componentUtil.isReactComponent(context, node)) {
return;
}
componentList.set(context, node);
}

@@ -49,0 +51,0 @@ };

@@ -34,6 +34,5 @@ /**

function isPropTypesUsage(node) {
return Boolean(
node.object.type === 'ThisExpression' &&
node.property.name === 'props'
);
var isClassUsage = node.object.type === 'ThisExpression' && node.property.name === 'props';
var isStatelessFunctionUsage = node.object.name === 'props';
return isClassUsage || isStatelessFunctionUsage;
}

@@ -94,3 +93,2 @@

component &&
component.isReactComponent &&
component.usedPropTypes &&

@@ -320,2 +318,5 @@ !component.ignorePropsValidation

function getPropertyName(node) {
if (componentUtil.getNode(context, node)) {
node = node.parent;
}
var property = node.property;

@@ -358,3 +359,3 @@ if (property) {

case 'MemberExpression':
name = getPropertyName(node.parent);
name = getPropertyName(node);
if (name) {

@@ -370,2 +371,3 @@ allNames = parentNames.concat(name);

node.parent.id.properties &&
node.parent.id.properties.length &&
getKeyValue(node.parent.id.properties[0])

@@ -406,3 +408,3 @@ ) {

allNames: allNames,
node: node.parent.property
node: node.object.name !== 'props' ? node.parent.property : node.property
});

@@ -412,3 +414,3 @@ break;

for (var k = 0, l = properties.length; k < l; k++) {
if (hasSpreadOperator(properties[k])) {
if (hasSpreadOperator(properties[k]) || properties[k].computed) {
continue;

@@ -529,2 +531,6 @@ }

ClassDeclaration: function(node) {
componentList.set(context, node);
},
ClassProperty: function(node) {

@@ -587,2 +593,3 @@ if (!isPropTypesDeclaration(node)) {

ObjectExpression: function(node) {
componentList.set(context, node);
// Search for the proptypes declaration

@@ -595,8 +602,6 @@ node.properties.forEach(function(property) {

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

@@ -613,11 +618,2 @@

}
},
ReturnStatement: function(node) {
if (!componentUtil.isReactComponent(context, node)) {
return;
}
componentList.set(context, node, {
isReactComponent: true
});
}

@@ -624,0 +620,0 @@ };

@@ -88,3 +88,2 @@ /**

component &&
component.isReactComponent &&
!component.hasDisplayName

@@ -350,5 +349,13 @@ );

return {
ClassDeclaration: function(node) {
componentList.set(context, node);
},
ObjectExpression: function(node) {
componentList.set(context, node);
},
'Program:exit': function() {
var list = componentList.getList();
for (var component in list) {

@@ -363,11 +370,2 @@ if (!list.hasOwnProperty(component) || !mustBeValidated(list[component])) {

reportErrors();
},
ReturnStatement: function(node) {
if (!componentUtil.isReactComponent(context, node)) {
return;
}
componentList.set(context, node, {
isReactComponent: true
});
}

@@ -374,0 +372,0 @@ };

@@ -12,18 +12,45 @@ /**

/**
* Detect if we are in a React Component
* A React component is defined has an object/class with a property "render"
* that return a JSXElement or null/false
* Detect if the node is a component definition
* @param {Object} context The current rule context.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are in a React Component, false if not.
* @returns {Boolean} True the node is a component definition, false if not.
*/
function isReactComponent(context, node) {
function isComponentDefinition(context, node) {
switch (node.type) {
case 'ObjectExpression':
if (node.parent && node.parent.callee && context.getSource(node.parent.callee) === 'React.createClass') {
return true;
}
break;
case 'ClassDeclaration':
var superClass = node.superClass && context.getSource(node.superClass);
if (superClass === 'Component' || superClass === 'React.Component') {
return true;
}
break;
default:
break;
}
return false;
}
/**
* Check if we are in a stateless function component
* @param {Object} context The current rule context.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if we are in a stateless function component, false if not.
*/
function isStatelessFunctionComponent(context, node) {
if (node.type !== 'ReturnStatement') {
throw new Error('React Component detection must be done from a ReturnStatement ASTNode');
return false;
}
var scope = context.getScope();
while (scope.upper && scope.type !== 'function') {
while (scope) {
if (scope.type === 'class') {
return false;
}
scope = scope.upper;
}
var returnsJSX =

@@ -39,32 +66,19 @@ node.argument &&

;
var isComponentRender =
(returnsJSX || returnsReactCreateElement) &&
scope.block.parent.key && scope.block.parent.key.name === 'render'
;
var isEmptyComponentRender =
node.argument &&
node.argument.type === 'Literal' && (node.argument.value === null || node.argument.value === false) &&
scope.block.parent.key && scope.block.parent.key.name === 'render'
;
return Boolean(isEmptyComponentRender || isComponentRender);
return Boolean(returnsJSX || returnsReactCreateElement);
}
/**
* Detect if the node is a component definition
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True the node is a component definition, false if not.
* Get the identifiers of a React component ASTNode
* @param {ASTNode} node The React component ASTNode being checked.
* @returns {Object} The component identifiers.
*/
function isComponentDefinition(node) {
var isES6Component = node.type === 'ClassDeclaration';
var isES5Component = Boolean(
node.type === 'ObjectExpression' &&
node.parent &&
node.parent.callee &&
node.parent.callee.object &&
node.parent.callee.property &&
node.parent.callee.object.name === 'React' &&
node.parent.callee.property.name === 'createClass'
);
return isES5Component || isES6Component;
function getIdentifiers(node) {
var name = node.id && node.id.name || DEFAULT_COMPONENT_NAME;
var id = name + ':' + node.loc.start.line + ':' + node.loc.start.column;
return {
id: id,
name: name
};
}

@@ -78,4 +92,3 @@

*/
function getNode(context, node) {
var componentNode = null;
function getNode(context, node, list) {
var ancestors = context.getAncestors().reverse();

@@ -86,24 +99,21 @@

for (var i = 0, j = ancestors.length; i < j; i++) {
if (isComponentDefinition(ancestors[i])) {
componentNode = ancestors[i];
break;
if (isComponentDefinition(context, ancestors[i])) {
return ancestors[i];
}
// Node is already in the component list
var identifiers = getIdentifiers(ancestors[i]);
if (list && list[identifiers.id]) {
return ancestors[i];
}
}
return componentNode;
}
if (isStatelessFunctionComponent(context, node)) {
var scope = context.getScope();
while (scope.upper && scope.type !== 'function') {
scope = scope.upper;
}
return scope.block;
}
/**
* Get the identifiers of a React component ASTNode
* @param {ASTNode} node The React component ASTNode being checked.
* @returns {Object} The component identifiers.
*/
function getIdentifiers(node) {
var name = node.id && node.id.name || DEFAULT_COMPONENT_NAME;
var id = name + ':' + node.loc.start.line + ':' + node.loc.start.column;
return {
id: id,
name: name
};
return null;
}

@@ -166,6 +176,7 @@

List.prototype.set = function(context, node, customProperties) {
var componentNode = getNode(context, node);
var componentNode = getNode(context, node, this._list);
if (!componentNode) {
return null;
}
var identifiers = getIdentifiers(componentNode);

@@ -219,3 +230,2 @@

DEFAULT_COMPONENT_NAME: DEFAULT_COMPONENT_NAME,
isReactComponent: isReactComponent,
getNode: getNode,

@@ -222,0 +232,0 @@ isComponentDefinition: isComponentDefinition,

{
"name": "eslint-plugin-react",
"version": "3.5.1",
"version": "3.6.0",
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",

@@ -28,4 +28,4 @@ "description": "React specific linting rules for ESLint",

"coveralls": "2.11.4",
"eslint": "1.5.1",
"istanbul": "0.3.21",
"eslint": "1.7.1",
"istanbul": "0.4.0",
"mocha": "2.3.3"

@@ -32,0 +32,0 @@ },

@@ -76,3 +76,4 @@ ESLint-plugin-React

"react/sort-comp": 1,
"react/wrap-multilines": 1
"react/wrap-multilines": 1,
"prefer-es6-class": 1,
}

@@ -112,2 +113,3 @@ }

* [wrap-multilines](docs/rules/wrap-multilines.md): Prevent missing parentheses around multilines JSX
* [prefer-es6-class](docs/rules/prefer-es6-class.md): Prefer es6 class instead of createClass for React Components

@@ -114,0 +116,0 @@ ## To Do

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc