diff --git a/CHANGELOG.md b/CHANGELOG.md index 288fb8d0f3..778e908d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## Unreleased +### Added +* [`sort-prop-types`]: give errors on TS types ([#3615][] @akulsr0) + +[#3615]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3615 + ## [7.33.2] - 2023.08.15 ### Fixed diff --git a/docs/rules/sort-prop-types.md b/docs/rules/sort-prop-types.md index ec3d9f2d71..70aa65a331 100644 --- a/docs/rules/sort-prop-types.md +++ b/docs/rules/sort-prop-types.md @@ -95,7 +95,8 @@ class Component extends React.Component { "ignoreCase": , "requiredFirst": , "sortShapeProp": , - "noSortAlphabetically": + "noSortAlphabetically": , + "checkTypes": }] ... ``` @@ -170,6 +171,10 @@ var Component = createReactClass({ }); ``` +### `checkTypes` + +When `true`, the sorting of prop type definitions are checked. + ## When Not To Use It This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing props declarations isn't a part of your coding standards, then you can leave this rule off. diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index 56e67033c7..a5b96c35bc 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -52,6 +52,9 @@ module.exports = { sortShapeProp: { type: 'boolean', }, + checkTypes: { + type: 'boolean', + }, }, additionalProperties: false, }], @@ -64,8 +67,14 @@ module.exports = { const ignoreCase = configuration.ignoreCase || false; const noSortAlphabetically = configuration.noSortAlphabetically || false; const sortShapeProp = configuration.sortShapeProp || false; + const checkTypes = configuration.checkTypes || false; + + const typeAnnotations = new Map(); function getKey(node) { + if (node.type === 'ObjectTypeProperty') { + return context.getSourceCode().getFirstToken(node).value; + } if (node.key && node.key.value) { return node.key.value; } @@ -215,7 +224,32 @@ module.exports = { } } - return { + function handleFunctionComponent(node) { + const firstArg = node.params[0].typeAnnotation && node.params[0].typeAnnotation.typeAnnotation; + if (firstArg && firstArg.type === 'TSTypeReference') { + const propType = typeAnnotations.get(firstArg.typeName.name) + && typeAnnotations.get(firstArg.typeName.name)[0]; + if (propType && propType.members) { + checkSorted(propType.members); + } + } else if (firstArg && firstArg.type === 'TSTypeLiteral') { + if (firstArg.members) { + checkSorted(firstArg.members); + } + } else if (firstArg && firstArg.type === 'GenericTypeAnnotation') { + const propType = typeAnnotations.get(firstArg.id.name) + && typeAnnotations.get(firstArg.id.name)[0]; + if (propType && propType.properties) { + checkSorted(propType.properties); + } + } else if (firstArg && firstArg.type === 'ObjectTypeAnnotation') { + if (firstArg.properties) { + checkSorted(firstArg.properties); + } + } + } + + return Object.assign({ CallExpression(node) { if (!sortShapeProp || !isShapeProp(node) || !(node.arguments && node.arguments[0])) { return; @@ -261,6 +295,38 @@ module.exports = { } }); }, - }; + }, checkTypes ? { + TSTypeLiteral(node) { + if (node && node.parent.id) { + const currentNode = [].concat( + typeAnnotations.get(node.parent.id.name) || [], + node + ); + typeAnnotations.set(node.parent.id.name, currentNode); + } + }, + + TypeAlias(node) { + if (node.right.type === 'ObjectTypeAnnotation') { + const currentNode = [].concat( + typeAnnotations.get(node.id.name) || [], + node.right + ); + typeAnnotations.set(node.id.name, currentNode); + } + }, + + TSTypeAliasDeclaration(node) { + if (node.typeAnnotation.type === 'TSTypeLiteral' || node.typeAnnotation.type === 'ObjectTypeAnnotation') { + const currentNode = [].concat( + typeAnnotations.get(node.id.name) || [], + node.typeAnnotation + ); + typeAnnotations.set(node.id.name, currentNode); + } + }, + FunctionDeclaration: handleFunctionComponent, + ArrowFunctionExpression: handleFunctionComponent, + } : null); }, }; diff --git a/tests/lib/rules/sort-prop-types.js b/tests/lib/rules/sort-prop-types.js index d7417c005a..a89ebeaf97 100644 --- a/tests/lib/rules/sort-prop-types.js +++ b/tests/lib/rules/sort-prop-types.js @@ -480,6 +480,19 @@ ruleTester.run('sort-prop-types', rule, { }); `, options: [{ callbacksLast: true, noSortAlphabetically: true }], + }, + { + code: ` + type Props = { + zzz: string; + aaa: string; + } + function Foo(props: Props) { + return null; + } + `, + features: ['types'], + options: [{ checkTypes: false }], } )), invalid: parsers.all([].concat( @@ -2250,6 +2263,91 @@ ruleTester.run('sort-prop-types', rule, { line: 4, }, ], - } : [] + } : [], + { + code: ` + type Props = { + zzz: string; + aaa: string; + } + function Foo(props: Props) { + return null; + } + `, + output: ` + type Props = { + aaa: string; + zzz: string; + } + function Foo(props: Props) { + return null; + } + `, + features: ['types'], + options: [{ checkTypes: true }], + errors: [ + { + messageId: 'propsNotSorted', + line: 4, + column: 11, + }, + ], + }, + { + code: ` + type Props = { + zzz: string; + aaa: string; + } + const Foo = (props: Props) => { + return null; + } + `, + output: ` + type Props = { + aaa: string; + zzz: string; + } + const Foo = (props: Props) => { + return null; + } + `, + features: ['types'], + options: [{ checkTypes: true }], + errors: [ + { + messageId: 'propsNotSorted', + line: 4, + column: 11, + }, + ], + }, + { + code: ` + const Foo = (props: { + zzz: string, + aaa: string, + }) => { + return null; + } + `, + output: ` + const Foo = (props: { + aaa: string, + zzz: string, + }) => { + return null; + } + `, + features: ['types'], + options: [{ checkTypes: true }], + errors: [ + { + messageId: 'propsNotSorted', + line: 4, + column: 11, + }, + ], + } )), });