diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d22ee42c..233604f584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,14 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`jsx-closing-tag-location`]: add `line-aligned` option ([#3777] @kimtaejin3) * [`no-danger`]: add `customComponentNames` option ([#3748][] @akulsr0) +### Fixed +* [`no-invalid-html-attribute`]: substitute placeholders in suggestion messages ([#3759][] @mdjermanovic) +* [`sort-prop-types`]: single line type ending without semicolon ([#3784][] @akulsr0) + ### Changed * [Refactor] `variableUtil`: Avoid creating a single flat variable scope for each lookup ([#3782][] @DanielRosenwasser) +[#3784]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3784 [#3782]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3782 [#3777]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3777 [#3774]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3774 @@ -28,9 +33,6 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange [#3724]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3724 [#3694]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3694 -### Fixed -* [`no-invalid-html-attribute`]: substitute placeholders in suggestion messages ([#3759][] @mdjermanovic) - ## [7.34.4] - 2024.07.13 ### Fixed diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index 20effacc44..b0087e59c2 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -129,7 +129,8 @@ module.exports = { requiredFirst, callbacksLast, noSortAlphabetically, - sortShapeProp + sortShapeProp, + checkTypes ); } diff --git a/lib/util/propTypesSort.js b/lib/util/propTypesSort.js index 12982430b3..bffcfb1073 100644 --- a/lib/util/propTypesSort.js +++ b/lib/util/propTypesSort.js @@ -126,6 +126,7 @@ const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start a * @param {Boolean=} callbacksLast whether or not to sort callbacks after everything else. * @param {Boolean=} noSortAlphabetically whether or not to disable alphabetical sorting of the elements. * @param {Boolean=} sortShapeProp whether or not to sort propTypes defined in PropTypes.shape. + * @param {Boolean=} checkTypes whether or not sorting of prop type definitions are checked. * @returns {Object|*|{range, text}} the sort order of the two elements. */ function fixPropTypesSort( @@ -136,7 +137,8 @@ function fixPropTypesSort( requiredFirst, callbacksLast, noSortAlphabetically, - sortShapeProp + sortShapeProp, + checkTypes ) { function sortInSource(allNodes, source) { const originalSource = source; @@ -183,10 +185,15 @@ function fixPropTypesSort( ); const sourceCodeText = getText(context); + let separator = ''; source = nodes.reduceRight((acc, attr, index) => { const sortedAttr = sortedAttributes[index]; const commentNode = commentnodeMap.get(sortedAttr); let sortedAttrText = sourceCodeText.slice(commentNode.start, commentNode.end); + const sortedAttrTextLastChar = sortedAttrText[sortedAttrText.length - 1]; + if (!separator && [';', ','].some((allowedSep) => sortedAttrTextLastChar === allowedSep)) { + separator = sortedAttrTextLastChar; + } if (sortShapeProp && isShapeProp(sortedAttr.value)) { const shape = getShapeProperties(sortedAttr.value); if (shape) { @@ -197,7 +204,8 @@ function fixPropTypesSort( sortedAttrText = attrSource.slice(sortedAttr.range[0], sortedAttr.range[1]); } } - return `${acc.slice(0, commentnodeMap.get(attr).start)}${sortedAttrText}${acc.slice(commentnodeMap.get(attr).end)}`; + const sortedAttrTextVal = checkTypes && !sortedAttrText.endsWith(separator) ? `${sortedAttrText}${separator}` : sortedAttrText; + return `${acc.slice(0, commentnodeMap.get(attr).start)}${sortedAttrTextVal}${acc.slice(commentnodeMap.get(attr).end)}`; }, source); }); return source; diff --git a/tests/lib/rules/sort-prop-types.js b/tests/lib/rules/sort-prop-types.js index a6157aa347..a902f54c4c 100644 --- a/tests/lib/rules/sort-prop-types.js +++ b/tests/lib/rules/sort-prop-types.js @@ -501,6 +501,18 @@ ruleTester.run('sort-prop-types', rule, { } `, options: [{ checkTypes: true }], + }, + { + code: ` + const Foo = (props: { + aaa: string, + zzz: string + }) => { + return null; + } + `, + features: ['types'], + options: [{ checkTypes: true }], } )), invalid: parsers.all([].concat( @@ -2356,6 +2368,52 @@ ruleTester.run('sort-prop-types', rule, { column: 11, }, ], + }, + { + code: ` + type CustomProps = { onChange: () => void; name: string }; + const Foo = (props: CustomProps) => { + return null; + } + `, + output: ` + type CustomProps = { name: string; onChange: () => void }; + const Foo = (props: CustomProps) => { + return null; + } + `, + features: ['types'], + options: [{ checkTypes: true }], + errors: [ + { + messageId: 'propsNotSorted', + line: 2, + column: 52, + }, + ], + }, + { + code: ` + type CustomProps = { onChange: (event: { target: { name: string; value: string } }) => void; name: string }; + const Foo = (props: CustomProps) => { + return null; + } + `, + output: ` + type CustomProps = { name: string; onChange: (event: { target: { name: string; value: string } }) => void }; + const Foo = (props: CustomProps) => { + return null; + } + `, + features: ['types'], + options: [{ checkTypes: true }], + errors: [ + { + messageId: 'propsNotSorted', + line: 2, + column: 102, + }, + ], } )), });