You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
4.8 KiB
161 lines
4.8 KiB
/** |
|
* @fileoverview Prefer exact proptype definitions |
|
*/ |
|
|
|
'use strict'; |
|
|
|
const Components = require('../util/Components'); |
|
const docsUrl = require('../util/docsUrl'); |
|
const propsUtil = require('../util/props'); |
|
const propWrapperUtil = require('../util/propWrapper'); |
|
const variableUtil = require('../util/variable'); |
|
const report = require('../util/report'); |
|
|
|
// ----------------------------------------------------------------------------- |
|
// Rule Definition |
|
// ----------------------------------------------------------------------------- |
|
|
|
const messages = { |
|
propTypes: 'Component propTypes should be exact by using {{exactPropWrappers}}.', |
|
flow: 'Component flow props should be set with exact objects.', |
|
}; |
|
|
|
module.exports = { |
|
meta: { |
|
docs: { |
|
description: 'Prefer exact proptype definitions', |
|
category: 'Possible Errors', |
|
recommended: false, |
|
url: docsUrl('prefer-exact-props'), |
|
}, |
|
messages, |
|
schema: [], |
|
}, |
|
|
|
create: Components.detect((context, components, utils) => { |
|
const typeAliases = {}; |
|
const exactWrappers = propWrapperUtil.getExactPropWrapperFunctions(context); |
|
const sourceCode = context.getSourceCode(); |
|
|
|
function getPropTypesErrorMessage() { |
|
const formattedWrappers = propWrapperUtil.formatPropWrapperFunctions(exactWrappers); |
|
const message = exactWrappers.size > 1 ? `one of ${formattedWrappers}` : formattedWrappers; |
|
return { exactPropWrappers: message }; |
|
} |
|
|
|
function isNonExactObjectTypeAnnotation(node) { |
|
return ( |
|
node |
|
&& node.type === 'ObjectTypeAnnotation' |
|
&& node.properties.length > 0 |
|
&& !node.exact |
|
); |
|
} |
|
|
|
function hasNonExactObjectTypeAnnotation(node) { |
|
const typeAnnotation = node.typeAnnotation; |
|
return ( |
|
typeAnnotation |
|
&& typeAnnotation.typeAnnotation |
|
&& isNonExactObjectTypeAnnotation(typeAnnotation.typeAnnotation) |
|
); |
|
} |
|
|
|
function hasGenericTypeAnnotation(node) { |
|
const typeAnnotation = node.typeAnnotation; |
|
return ( |
|
typeAnnotation |
|
&& typeAnnotation.typeAnnotation |
|
&& typeAnnotation.typeAnnotation.type === 'GenericTypeAnnotation' |
|
); |
|
} |
|
|
|
function isNonEmptyObjectExpression(node) { |
|
return ( |
|
node |
|
&& node.type === 'ObjectExpression' |
|
&& node.properties.length > 0 |
|
); |
|
} |
|
|
|
function isNonExactPropWrapperFunction(node) { |
|
return ( |
|
node |
|
&& node.type === 'CallExpression' |
|
&& !propWrapperUtil.isExactPropWrapperFunction(context, sourceCode.getText(node.callee)) |
|
); |
|
} |
|
|
|
function reportPropTypesError(node) { |
|
report(context, messages.propTypes, 'propTypes', { |
|
node, |
|
data: getPropTypesErrorMessage(), |
|
}); |
|
} |
|
|
|
function reportFlowError(node) { |
|
report(context, messages.flow, 'flow', { |
|
node, |
|
}); |
|
} |
|
|
|
return { |
|
TypeAlias(node) { |
|
// working around an issue with eslint@3 and babel-eslint not finding the TypeAlias in scope |
|
typeAliases[node.id.name] = node; |
|
}, |
|
|
|
'ClassProperty, PropertyDefinition'(node) { |
|
if (!propsUtil.isPropTypesDeclaration(node)) { |
|
return; |
|
} |
|
|
|
if (hasNonExactObjectTypeAnnotation(node)) { |
|
reportFlowError(node); |
|
} else if (exactWrappers.size > 0 && isNonEmptyObjectExpression(node.value)) { |
|
reportPropTypesError(node); |
|
} else if (exactWrappers.size > 0 && isNonExactPropWrapperFunction(node.value)) { |
|
reportPropTypesError(node); |
|
} |
|
}, |
|
|
|
Identifier(node) { |
|
if (!utils.getStatelessComponent(node.parent)) { |
|
return; |
|
} |
|
|
|
if (hasNonExactObjectTypeAnnotation(node)) { |
|
reportFlowError(node); |
|
} else if (hasGenericTypeAnnotation(node)) { |
|
const identifier = node.typeAnnotation.typeAnnotation.id.name; |
|
const typeAlias = typeAliases[identifier]; |
|
const propsDefinition = typeAlias ? typeAlias.right : null; |
|
if (isNonExactObjectTypeAnnotation(propsDefinition)) { |
|
reportFlowError(node); |
|
} |
|
} |
|
}, |
|
|
|
MemberExpression(node) { |
|
if (!propsUtil.isPropTypesDeclaration(node) || exactWrappers.size === 0) { |
|
return; |
|
} |
|
|
|
const right = node.parent.right; |
|
if (isNonEmptyObjectExpression(right)) { |
|
reportPropTypesError(node); |
|
} else if (isNonExactPropWrapperFunction(right)) { |
|
reportPropTypesError(node); |
|
} else if (right.type === 'Identifier') { |
|
const identifier = right.name; |
|
const propsDefinition = variableUtil.findVariableByName(context, identifier); |
|
if (isNonEmptyObjectExpression(propsDefinition)) { |
|
reportPropTypesError(node); |
|
} else if (isNonExactPropWrapperFunction(propsDefinition)) { |
|
reportPropTypesError(node); |
|
} |
|
} |
|
}, |
|
}; |
|
}), |
|
};
|
|
|