 c177363a19
			
		
	
	c177363a19
	
	
	
		
			
			🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			156 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @fileoverview Limit maximum of props on a single line in JSX
 | |
|  * @author Yannick Croissant
 | |
|  */
 | |
| 
 | |
| 'use strict';
 | |
| 
 | |
| const docsUrl = require('../util/docsUrl');
 | |
| const getText = require('../util/eslint').getText;
 | |
| const report = require('../util/report');
 | |
| 
 | |
| function getPropName(context, propNode) {
 | |
|   if (propNode.type === 'JSXSpreadAttribute') {
 | |
|     return getText(context, propNode.argument);
 | |
|   }
 | |
|   return propNode.name.name;
 | |
| }
 | |
| 
 | |
| // ------------------------------------------------------------------------------
 | |
| // Rule Definition
 | |
| // ------------------------------------------------------------------------------
 | |
| 
 | |
| const messages = {
 | |
|   newLine: 'Prop `{{prop}}` must be placed on a new line',
 | |
| };
 | |
| 
 | |
| /** @type {import('eslint').Rule.RuleModule} */
 | |
| module.exports = {
 | |
|   meta: {
 | |
|     docs: {
 | |
|       description: 'Enforce maximum of props on a single line in JSX',
 | |
|       category: 'Stylistic Issues',
 | |
|       recommended: false,
 | |
|       url: docsUrl('jsx-max-props-per-line'),
 | |
|     },
 | |
|     fixable: 'code',
 | |
| 
 | |
|     messages,
 | |
| 
 | |
|     schema: [{
 | |
|       anyOf: [{
 | |
|         type: 'object',
 | |
|         properties: {
 | |
|           maximum: {
 | |
|             type: 'object',
 | |
|             properties: {
 | |
|               single: {
 | |
|                 type: 'integer',
 | |
|                 minimum: 1,
 | |
|               },
 | |
|               multi: {
 | |
|                 type: 'integer',
 | |
|                 minimum: 1,
 | |
|               },
 | |
|             },
 | |
|           },
 | |
|         },
 | |
|         additionalProperties: false,
 | |
|       }, {
 | |
|         type: 'object',
 | |
|         properties: {
 | |
|           maximum: {
 | |
|             type: 'number',
 | |
|             minimum: 1,
 | |
|           },
 | |
|           when: {
 | |
|             type: 'string',
 | |
|             enum: ['always', 'multiline'],
 | |
|           },
 | |
|         },
 | |
|         additionalProperties: false,
 | |
|       }],
 | |
|     }],
 | |
|   },
 | |
| 
 | |
|   create(context) {
 | |
|     const configuration = context.options[0] || {};
 | |
|     const maximum = configuration.maximum || 1;
 | |
| 
 | |
|     const maxConfig = typeof maximum === 'number'
 | |
|       ? {
 | |
|         single: configuration.when === 'multiline' ? Infinity : maximum,
 | |
|         multi: maximum,
 | |
|       }
 | |
|       : {
 | |
|         single: maximum.single || Infinity,
 | |
|         multi: maximum.multi || Infinity,
 | |
|       };
 | |
| 
 | |
|     function generateFixFunction(line, max) {
 | |
|       const output = [];
 | |
|       const front = line[0].range[0];
 | |
|       const back = line[line.length - 1].range[1];
 | |
| 
 | |
|       for (let i = 0; i < line.length; i += max) {
 | |
|         const nodes = line.slice(i, i + max);
 | |
|         output.push(nodes.reduce((prev, curr) => {
 | |
|           if (prev === '') {
 | |
|             return getText(context, curr);
 | |
|           }
 | |
|           return `${prev} ${getText(context, curr)}`;
 | |
|         }, ''));
 | |
|       }
 | |
| 
 | |
|       const code = output.join('\n');
 | |
| 
 | |
|       return function fix(fixer) {
 | |
|         return fixer.replaceTextRange([front, back], code);
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     return {
 | |
|       JSXOpeningElement(node) {
 | |
|         if (!node.attributes.length) {
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         const isSingleLineTag = node.loc.start.line === node.loc.end.line;
 | |
| 
 | |
|         if ((isSingleLineTag ? maxConfig.single : maxConfig.multi) === Infinity) {
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         const firstProp = node.attributes[0];
 | |
|         const linePartitionedProps = [[firstProp]];
 | |
| 
 | |
|         node.attributes.reduce((last, decl) => {
 | |
|           if (last.loc.end.line === decl.loc.start.line) {
 | |
|             linePartitionedProps[linePartitionedProps.length - 1].push(decl);
 | |
|           } else {
 | |
|             linePartitionedProps.push([decl]);
 | |
|           }
 | |
|           return decl;
 | |
|         });
 | |
| 
 | |
|         linePartitionedProps.forEach((propsInLine) => {
 | |
|           const maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line
 | |
|             ? maxConfig.single
 | |
|             : maxConfig.multi;
 | |
| 
 | |
|           if (propsInLine.length > maxPropsCountPerLine) {
 | |
|             const name = getPropName(context, propsInLine[maxPropsCountPerLine]);
 | |
|             report(context, messages.newLine, 'newLine', {
 | |
|               node: propsInLine[maxPropsCountPerLine],
 | |
|               data: {
 | |
|                 prop: name,
 | |
|               },
 | |
|               fix: generateFixFunction(propsInLine, maxPropsCountPerLine),
 | |
|             });
 | |
|           }
 | |
|         });
 | |
|       },
 | |
|     };
 | |
|   },
 | |
| };
 |