eslint-plugin-react
Advanced tools
Comparing version 3.14.0 to 3.15.0
@@ -6,5 +6,23 @@ # Change Log | ||
## [3.15.0] - 2016-01-12 | ||
### Added | ||
* Add support for flow annotations to `prop-types` ([#382][] @phpnode) | ||
### Fixed | ||
* Fix `prop-types` crash when initializing class variable with an empty object ([#383][]) | ||
* Fix `prop-types` crash when propTypes are using the spread operator ([#389][]) | ||
### Changed | ||
* Improve `sort-comp` error messages ([#372][] @SystemParadox) | ||
* Update dependencies | ||
[3.15.0]: https://github.com/yannickcr/eslint-plugin-react/compare/v3.14.0...v3.15.0 | ||
[#382]: https://github.com/yannickcr/eslint-plugin-react/pull/382 | ||
[#383]: https://github.com/yannickcr/eslint-plugin-react/issues/383 | ||
[#389]: https://github.com/yannickcr/eslint-plugin-react/issues/389 | ||
[#372]: https://github.com/yannickcr/eslint-plugin-react/pull/372 | ||
## [3.14.0] - 2016-01-05 | ||
### Added | ||
* Add `jsx-intent` rule ([#342][]) | ||
* Add `jsx-indent` rule ([#342][]) | ||
* Add shared setting for pragma configuration ([#228][] @NickStefan) | ||
@@ -11,0 +29,0 @@ |
@@ -22,2 +22,5 @@ /** | ||
var customValidators = configuration.customValidators || []; | ||
// Used to track the type annotations in scope. | ||
// Necessary because babel's scopes do not track type annotations. | ||
var stack = null; | ||
@@ -27,2 +30,18 @@ var MISSING_MESSAGE = '\'{{name}}\' is missing in props validation'; | ||
/** | ||
* Helper for accessing the current scope in the stack. | ||
* @param {string} key The name of the identifier to access. If omitted, returns the full scope. | ||
* @param {ASTNode} value If provided sets the new value for the identifier. | ||
* @returns {Object|ASTNode} Either the whole scope or the ASTNode associated with the given identifier. | ||
*/ | ||
function typeScope(key, value) { | ||
if (arguments.length === 0) { | ||
return stack[stack.length - 1]; | ||
} else if (arguments.length === 1) { | ||
return stack[stack.length - 1][key]; | ||
} | ||
stack[stack.length - 1][key] = value; | ||
return value; | ||
} | ||
/** | ||
* Checks if we are using a prop | ||
@@ -42,2 +61,22 @@ * @param {ASTNode} node The AST node being checked. | ||
/** | ||
* Checks if we are declaring a `props` class property with a flow type annotation. | ||
* @param {ASTNode} node The AST node being checked. | ||
* @returns {Boolean} True if the node is a type annotated props declaration, false if not. | ||
*/ | ||
function isAnnotatedPropsDeclaration(node) { | ||
if (node && node.type === 'ClassProperty') { | ||
var tokens = context.getFirstTokens(node, 2); | ||
if ( | ||
node.typeAnnotation && ( | ||
tokens[0].value === 'props' || | ||
(tokens[1] && tokens[1].value === 'props') | ||
) | ||
) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Checks if we are declaring a prop | ||
@@ -195,2 +234,6 @@ * @param {ASTNode} node The AST node being checked. | ||
function getKeyValue(node) { | ||
if (node.type === 'ObjectTypeProperty') { | ||
var tokens = context.getFirstTokens(node, 1); | ||
return tokens[0].value; | ||
} | ||
var key = node.key || node.argument; | ||
@@ -221,3 +264,3 @@ return key.type === 'Identifier' ? key.name : key.value; | ||
* The representation is used to verify nested used properties. | ||
* @param {ASTNode} value Node of the React.PropTypes for the desired propery | ||
* @param {ASTNode} value Node of the React.PropTypes for the desired property | ||
* @return {Object|Boolean} The representation of the declaration, true means | ||
@@ -237,2 +280,3 @@ * the property is declared without the need for further analysis. | ||
if ( | ||
value && | ||
value.type === 'MemberExpression' && | ||
@@ -248,2 +292,3 @@ value.property && | ||
if ( | ||
value && | ||
value.type === 'CallExpression' && | ||
@@ -326,2 +371,61 @@ value.callee && | ||
/** | ||
* Creates the representation of the React props type annotation for the component. | ||
* The representation is used to verify nested used properties. | ||
* @param {ASTNode} annotation Type annotation for the props class property. | ||
* @return {Object|Boolean} The representation of the declaration, true means | ||
* the property is declared without the need for further analysis. | ||
*/ | ||
function buildTypeAnnotationDeclarationTypes(annotation) { | ||
switch (annotation.type) { | ||
case 'GenericTypeAnnotation': | ||
if (typeScope(annotation.id.name)) { | ||
return buildTypeAnnotationDeclarationTypes(typeScope(annotation.id.name)); | ||
} | ||
return true; | ||
case 'ObjectTypeAnnotation': | ||
var shapeTypeDefinition = { | ||
type: 'shape', | ||
children: {} | ||
}; | ||
iterateProperties(annotation.properties, function(childKey, childValue) { | ||
shapeTypeDefinition.children[childKey] = buildTypeAnnotationDeclarationTypes(childValue); | ||
}); | ||
return shapeTypeDefinition; | ||
case 'UnionTypeAnnotation': | ||
var unionTypeDefinition = { | ||
type: 'union', | ||
children: [] | ||
}; | ||
for (var i = 0, j = annotation.types.length; i < j; i++) { | ||
var type = buildTypeAnnotationDeclarationTypes(annotation.types[i]); | ||
// keep only complex type | ||
if (type !== true) { | ||
if (type.children === true) { | ||
// every child is accepted for one type, abort type analysis | ||
unionTypeDefinition.children = true; | ||
return unionTypeDefinition; | ||
} | ||
} | ||
unionTypeDefinition.children.push(type); | ||
} | ||
if (unionTypeDefinition.children.length === 0) { | ||
// no complex type found, simply accept everything | ||
return true; | ||
} | ||
return unionTypeDefinition; | ||
case 'ArrayTypeAnnotation': | ||
return { | ||
type: 'object', | ||
children: { | ||
__ANY_KEY__: buildTypeAnnotationDeclarationTypes(annotation.elementType) | ||
} | ||
}; | ||
default: | ||
// Unknown or accepts everything. | ||
return true; | ||
} | ||
} | ||
/** | ||
* Check if we are in a class constructor | ||
@@ -499,4 +603,13 @@ * @return {boolean} true if we are in a class constructor, false if not | ||
switch (propTypes && propTypes.type) { | ||
case 'ObjectTypeAnnotation': | ||
iterateProperties(propTypes.properties, function(key, value) { | ||
declaredPropTypes[key] = buildTypeAnnotationDeclarationTypes(value); | ||
}); | ||
break; | ||
case 'ObjectExpression': | ||
iterateProperties(propTypes.properties, function(key, value) { | ||
if (!value) { | ||
ignorePropsValidation = true; | ||
return; | ||
} | ||
declaredPropTypes[key] = buildReactDeclarationTypes(value); | ||
@@ -579,2 +692,23 @@ }); | ||
/** | ||
* Resolve the type annotation for a given node. | ||
* Flow annotations are sometimes wrapped in outer `TypeAnnotation` | ||
* and `NullableTypeAnnotation` nodes which obscure the annotation we're | ||
* interested in. | ||
* This method also resolves type aliases where possible. | ||
* | ||
* @param {ASTNode} node The annotation or a node containing the type annotation. | ||
* @returns {ASTNode} The resolved type annotation for the node. | ||
*/ | ||
function resolveTypeAnnotation(node) { | ||
var annotation = node.typeAnnotation || node; | ||
while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) { | ||
annotation = annotation.typeAnnotation; | ||
} | ||
if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) { | ||
return typeScope(annotation.id.name); | ||
} | ||
return annotation; | ||
} | ||
// -------------------------------------------------------------------------- | ||
@@ -586,6 +720,7 @@ // Public | ||
ClassProperty: function(node) { | ||
if (!isPropTypesDeclaration(node)) { | ||
return; | ||
if (isAnnotatedPropsDeclaration(node)) { | ||
markPropTypesAsDeclared(node, resolveTypeAnnotation(node)); | ||
} else if (isPropTypesDeclaration(node)) { | ||
markPropTypesAsDeclared(node, node.value); | ||
} | ||
markPropTypesAsDeclared(node, node.value); | ||
}, | ||
@@ -657,3 +792,20 @@ | ||
TypeAlias: function(node) { | ||
typeScope(node.id.name, node.right); | ||
}, | ||
Program: function() { | ||
stack = [{}]; | ||
}, | ||
BlockStatement: function () { | ||
stack.push(Object.create(typeScope())); | ||
}, | ||
'BlockStatement:exit': function () { | ||
stack.pop(); | ||
}, | ||
'Program:exit': function() { | ||
stack = null; | ||
var list = components.list(); | ||
@@ -660,0 +812,0 @@ // Report undeclared proptypes for all classes |
@@ -45,3 +45,3 @@ /** | ||
var MISPOSITION_MESSAGE = '{{propA}} must be placed {{position}} {{propB}}'; | ||
var MISPOSITION_MESSAGE = '{{propA}} should be placed {{position}} {{propB}}'; | ||
@@ -48,0 +48,0 @@ var methodsOrder = getMethodsOrder({ |
@@ -307,3 +307,3 @@ /** | ||
} | ||
if (!node) { | ||
if (!node || !node.value) { | ||
return null; | ||
@@ -310,0 +310,0 @@ } |
{ | ||
"name": "eslint-plugin-react", | ||
"version": "3.14.0", | ||
"version": "3.15.0", | ||
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>", | ||
@@ -28,4 +28,4 @@ "description": "React specific linting rules for ESLint", | ||
"coveralls": "2.11.6", | ||
"eslint": "2.0.0-alpha-2", | ||
"istanbul": "0.4.1", | ||
"eslint": "2.0.0-beta.1", | ||
"istanbul": "0.4.2", | ||
"mocha": "2.3.4" | ||
@@ -32,0 +32,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
183828
4162