 c177363a19
			
		
	
	c177363a19
	
	
	
		
			
			🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			163 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @fileoverview Prefer exact proptype definitions
 | |
|  */
 | |
| 
 | |
| 'use strict';
 | |
| 
 | |
| const Components = require('../util/Components');
 | |
| const docsUrl = require('../util/docsUrl');
 | |
| const astUtil = require('../util/ast');
 | |
| const propsUtil = require('../util/props');
 | |
| const propWrapperUtil = require('../util/propWrapper');
 | |
| const variableUtil = require('../util/variable');
 | |
| const report = require('../util/report');
 | |
| const getText = require('../util/eslint').getText;
 | |
| 
 | |
| // -----------------------------------------------------------------------------
 | |
| // Rule Definition
 | |
| // -----------------------------------------------------------------------------
 | |
| 
 | |
| const messages = {
 | |
|   propTypes: 'Component propTypes should be exact by using {{exactPropWrappers}}.',
 | |
|   flow: 'Component flow props should be set with exact objects.',
 | |
| };
 | |
| 
 | |
| /** @type {import('eslint').Rule.RuleModule} */
 | |
| 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);
 | |
| 
 | |
|     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 (
 | |
|         astUtil.isCallExpression(node)
 | |
|         && !propWrapperUtil.isExactPropWrapperFunction(context, getText(context, 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, node, identifier);
 | |
|           if (isNonEmptyObjectExpression(propsDefinition)) {
 | |
|             reportPropTypesError(node);
 | |
|           } else if (isNonExactPropWrapperFunction(propsDefinition)) {
 | |
|             reportPropTypesError(node);
 | |
|           }
 | |
|         }
 | |
|       },
 | |
|     };
 | |
|   }),
 | |
| };
 |