diff --git a/rules/sort-classes.ts b/rules/sort-classes.ts index 45b9ce43a..d68e698a3 100644 --- a/rules/sort-classes.ts +++ b/rules/sort-classes.ts @@ -42,7 +42,6 @@ import { isNodeEslintDisabled } from '../utils/is-node-eslint-disabled' import { hasPartitionComment } from '../utils/has-partition-comment' import { sortNodesByGroups } from '../utils/sort-nodes-by-groups' import { getCommentsBefore } from '../utils/get-comments-before' -import { makeNewlinesFixes } from '../utils/make-newlines-fixes' import { getNewlinesErrors } from '../utils/get-newlines-errors' import { createEslintRule } from '../utils/create-eslint-rule' import { getLinesBetween } from '../utils/get-lines-between' @@ -52,8 +51,8 @@ import { toSingleLine } from '../utils/to-single-line' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { useGroups } from '../utils/use-groups' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' import { matches } from '../utils/matches' @@ -652,22 +651,6 @@ export default createEslintRule({ for (let messageId of messageIds) { context.report({ - fix: (fixer: TSESLint.RuleFixer) => [ - ...makeFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - sourceCode, - options, - fixer, - nodes, - }), - ...makeNewlinesFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - sourceCode, - options, - fixer, - nodes, - }), - ], data: { nodeDependentOnRight: firstUnorderedNodeDependentOnRight?.name, right: toSingleLine(right.name), @@ -675,6 +658,14 @@ export default createEslintRule({ rightGroup: right.group, leftGroup: left.group, }, + fix: (fixer: TSESLint.RuleFixer) => + makeFixes({ + sortedNodes: sortedNodesExcludingEslintDisabled, + sourceCode, + options, + fixer, + nodes, + }), node: right.node, messageId, }) diff --git a/rules/sort-decorators.ts b/rules/sort-decorators.ts index ab2beca62..96b089518 100644 --- a/rules/sort-decorators.ts +++ b/rules/sort-decorators.ts @@ -31,8 +31,8 @@ import { toSingleLine } from '../utils/to-single-line' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { useGroups } from '../utils/use-groups' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' diff --git a/rules/sort-enums.ts b/rules/sort-enums.ts index 9794547b7..7b97e7c93 100644 --- a/rules/sort-enums.ts +++ b/rules/sort-enums.ts @@ -31,8 +31,8 @@ import { toSingleLine } from '../utils/to-single-line' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { sortNodes } from '../utils/sort-nodes' import { makeFixes } from '../utils/make-fixes' +import { sortNodes } from '../utils/sort-nodes' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' diff --git a/rules/sort-exports.ts b/rules/sort-exports.ts index 3ced890b0..c952d7c13 100644 --- a/rules/sort-exports.ts +++ b/rules/sort-exports.ts @@ -23,8 +23,8 @@ import { getLinesBetween } from '../utils/get-lines-between' import { getSourceCode } from '../utils/get-source-code' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' -import { sortNodes } from '../utils/sort-nodes' import { makeFixes } from '../utils/make-fixes' +import { sortNodes } from '../utils/sort-nodes' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' diff --git a/rules/sort-heritage-clauses.ts b/rules/sort-heritage-clauses.ts index 0ab620028..208c1d6a1 100644 --- a/rules/sort-heritage-clauses.ts +++ b/rules/sort-heritage-clauses.ts @@ -26,8 +26,8 @@ import { toSingleLine } from '../utils/to-single-line' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { useGroups } from '../utils/use-groups' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' @@ -176,12 +176,6 @@ let sortHeritageClauses = ( let leftNumber = getGroupNumber(options.groups, left) let rightNumber = getGroupNumber(options.groups, right) context.report({ - data: { - right: toSingleLine(right.name), - left: toSingleLine(left.name), - rightGroup: right.group, - leftGroup: left.group, - }, fix: fixer => makeFixes({ sortedNodes: sortedNodesExcludingEslintDisabled, @@ -189,6 +183,12 @@ let sortHeritageClauses = ( fixer, nodes, }), + data: { + right: toSingleLine(right.name), + left: toSingleLine(left.name), + rightGroup: right.group, + leftGroup: left.group, + }, messageId: leftNumber === rightNumber ? 'unexpectedHeritageClausesOrder' diff --git a/rules/sort-imports.ts b/rules/sort-imports.ts index a9eaf163e..c01ef2acb 100644 --- a/rules/sort-imports.ts +++ b/rules/sort-imports.ts @@ -28,7 +28,6 @@ import { hasPartitionComment } from '../utils/has-partition-comment' import { createNodeIndexMap } from '../utils/create-node-index-map' import { sortNodesByGroups } from '../utils/sort-nodes-by-groups' import { getCommentsBefore } from '../utils/get-comments-before' -import { makeNewlinesFixes } from '../utils/make-newlines-fixes' import { getNewlinesErrors } from '../utils/get-newlines-errors' import { createEslintRule } from '../utils/create-eslint-rule' import { getLinesBetween } from '../utils/get-lines-between' @@ -37,8 +36,8 @@ import { getSourceCode } from '../utils/get-source-code' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { useGroups } from '../utils/use-groups' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' import { matches } from '../utils/matches' @@ -571,15 +570,8 @@ export default createEslintRule, MESSAGE_ID>({ for (let messageId of messageIds) { context.report({ - fix: fixer => [ - ...makeFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - nodes: nodeList, - sourceCode, - options, - fixer, - }), - ...makeNewlinesFixes({ + fix: fixer => + makeFixes({ options: { ...options, customGroups: [], @@ -589,7 +581,6 @@ export default createEslintRule, MESSAGE_ID>({ sourceCode, fixer, }), - ], data: { rightGroup: right.group, leftGroup: left.group, diff --git a/rules/sort-jsx-props.ts b/rules/sort-jsx-props.ts index 63dae549d..8782c10b2 100644 --- a/rules/sort-jsx-props.ts +++ b/rules/sort-jsx-props.ts @@ -24,8 +24,8 @@ import { getSourceCode } from '../utils/get-source-code' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { useGroups } from '../utils/use-groups' import { pairwise } from '../utils/pairwise' import { complete } from '../utils/complete' import { matches } from '../utils/matches' diff --git a/rules/sort-maps.ts b/rules/sort-maps.ts index d0028ad93..79f971943 100644 --- a/rules/sort-maps.ts +++ b/rules/sort-maps.ts @@ -25,8 +25,8 @@ import { toSingleLine } from '../utils/to-single-line' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { sortNodes } from '../utils/sort-nodes' import { makeFixes } from '../utils/make-fixes' +import { sortNodes } from '../utils/sort-nodes' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' diff --git a/rules/sort-modules.ts b/rules/sort-modules.ts index 9fc3c8471..6a13a919f 100644 --- a/rules/sort-modules.ts +++ b/rules/sort-modules.ts @@ -44,7 +44,6 @@ import { hasPartitionComment } from '../utils/has-partition-comment' import { createNodeIndexMap } from '../utils/create-node-index-map' import { sortNodesByGroups } from '../utils/sort-nodes-by-groups' import { getNewlinesErrors } from '../utils/get-newlines-errors' -import { makeNewlinesFixes } from '../utils/make-newlines-fixes' import { getCommentsBefore } from '../utils/get-comments-before' import { getNodeDecorators } from '../utils/get-node-decorators' import { createEslintRule } from '../utils/create-eslint-rule' @@ -56,8 +55,8 @@ import { toSingleLine } from '../utils/to-single-line' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { useGroups } from '../utils/use-groups' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' @@ -442,22 +441,6 @@ let analyzeModule = ({ for (let messageId of messageIds) { context.report({ - fix: (fixer: TSESLint.RuleFixer) => [ - ...makeFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - sourceCode, - options, - fixer, - nodes, - }), - ...makeNewlinesFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - sourceCode, - options, - fixer, - nodes, - }), - ], data: { nodeDependentOnRight: firstUnorderedNodeDependentOnRight?.name, right: toSingleLine(right.name), @@ -465,6 +448,14 @@ let analyzeModule = ({ rightGroup: right.group, leftGroup: left.group, }, + fix: (fixer: TSESLint.RuleFixer) => + makeFixes({ + sortedNodes: sortedNodesExcludingEslintDisabled, + sourceCode, + options, + fixer, + nodes, + }), node: right.node, messageId, }) diff --git a/rules/sort-named-exports.ts b/rules/sort-named-exports.ts index 0b400e925..b15c6ebd7 100644 --- a/rules/sort-named-exports.ts +++ b/rules/sort-named-exports.ts @@ -24,8 +24,8 @@ import { getSourceCode } from '../utils/get-source-code' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { sortNodes } from '../utils/sort-nodes' import { makeFixes } from '../utils/make-fixes' +import { sortNodes } from '../utils/sort-nodes' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' diff --git a/rules/sort-named-imports.ts b/rules/sort-named-imports.ts index 9076a932c..c8ec80065 100644 --- a/rules/sort-named-imports.ts +++ b/rules/sort-named-imports.ts @@ -24,8 +24,8 @@ import { getSourceCode } from '../utils/get-source-code' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { sortNodes } from '../utils/sort-nodes' import { makeFixes } from '../utils/make-fixes' +import { sortNodes } from '../utils/sort-nodes' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' diff --git a/rules/sort-object-types.ts b/rules/sort-object-types.ts index 57139d72c..e6567b542 100644 --- a/rules/sort-object-types.ts +++ b/rules/sort-object-types.ts @@ -41,7 +41,6 @@ import { isNodeFunctionType } from '../utils/is-node-function-type' import { createNodeIndexMap } from '../utils/create-node-index-map' import { sortNodesByGroups } from '../utils/sort-nodes-by-groups' import { getCommentsBefore } from '../utils/get-comments-before' -import { makeNewlinesFixes } from '../utils/make-newlines-fixes' import { getNewlinesErrors } from '../utils/get-newlines-errors' import { createEslintRule } from '../utils/create-eslint-rule' import { getLinesBetween } from '../utils/get-lines-between' @@ -447,22 +446,14 @@ export let sortObjectTypeElements = ({ for (let messageId of messageIds) { context.report({ - fix: fixer => [ - ...makeFixes({ + fix: fixer => + makeFixes({ sortedNodes: sortedNodesExcludingEslintDisabled, sourceCode, options, fixer, nodes, }), - ...makeNewlinesFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - sourceCode, - options, - fixer, - nodes, - }), - ], data: { right: toSingleLine(right.name), left: toSingleLine(left.name), diff --git a/rules/sort-objects.ts b/rules/sort-objects.ts index 7f31e0aa6..1445a0b58 100644 --- a/rules/sort-objects.ts +++ b/rules/sort-objects.ts @@ -39,7 +39,6 @@ import { singleCustomGroupJsonSchema } from './sort-objects/types' import { sortNodesByGroups } from '../utils/sort-nodes-by-groups' import { allModifiers, allSelectors } from './sort-objects/types' import { getCommentsBefore } from '../utils/get-comments-before' -import { makeNewlinesFixes } from '../utils/make-newlines-fixes' import { getNewlinesErrors } from '../utils/get-newlines-errors' import { createEslintRule } from '../utils/create-eslint-rule' import { getLinesBetween } from '../utils/get-lines-between' @@ -48,8 +47,8 @@ import { getSourceCode } from '../utils/get-source-code' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { useGroups } from '../utils/use-groups' import { sortNodes } from '../utils/sort-nodes' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' @@ -502,22 +501,6 @@ export default createEslintRule({ for (let messageId of messageIds) { context.report({ - fix: fixer => [ - ...makeFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - sourceCode, - options, - fixer, - nodes, - }), - ...makeNewlinesFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - sourceCode, - options, - fixer, - nodes, - }), - ], data: { nodeDependentOnRight: firstUnorderedNodeDependentOnRight?.name, rightGroup: right.group, @@ -525,6 +508,14 @@ export default createEslintRule({ right: right.name, left: left.name, }, + fix: fixer => + makeFixes({ + sortedNodes: sortedNodesExcludingEslintDisabled, + sourceCode, + options, + fixer, + nodes, + }), node: right.node, messageId, }) diff --git a/rules/sort-switch-case.ts b/rules/sort-switch-case.ts index 5c3e7e10f..d280aaf36 100644 --- a/rules/sort-switch-case.ts +++ b/rules/sort-switch-case.ts @@ -11,8 +11,8 @@ import { localesJsonSchema, orderJsonSchema, } from '../utils/common-json-schemas' +import { makeSingleNodeCommentAfterFixes } from '../utils/make-single-node-comment-after-fixes' import { validateCustomSortConfiguration } from '../utils/validate-custom-sort-configuration' -import { makeCommentAfterFixes } from '../utils/make-comment-after-fixes' import { createNodeIndexMap } from '../utils/create-node-index-map' import { createEslintRule } from '../utils/create-eslint-rule' import { getSourceCode } from '../utils/get-source-code' @@ -182,13 +182,13 @@ export default createEslintRule({ lastCaseRange, sourceCode.getText(defaultCase.node), ), - ...makeCommentAfterFixes({ + ...makeSingleNodeCommentAfterFixes({ sortedNode: punctuatorAfterLastCase, node: defaultCase.node, sourceCode, fixer, }), - ...makeCommentAfterFixes({ + ...makeSingleNodeCommentAfterFixes({ node: punctuatorAfterLastCase, sortedNode: defaultCase.node, sourceCode, diff --git a/rules/sort-union-types.ts b/rules/sort-union-types.ts index 492b1f2ee..58bb45c47 100644 --- a/rules/sort-union-types.ts +++ b/rules/sort-union-types.ts @@ -25,7 +25,6 @@ import { hasPartitionComment } from '../utils/has-partition-comment' import { createNodeIndexMap } from '../utils/create-node-index-map' import { sortNodesByGroups } from '../utils/sort-nodes-by-groups' import { getCommentsBefore } from '../utils/get-comments-before' -import { makeNewlinesFixes } from '../utils/make-newlines-fixes' import { getNewlinesErrors } from '../utils/get-newlines-errors' import { createEslintRule } from '../utils/create-eslint-rule' import { getLinesBetween } from '../utils/get-lines-between' @@ -34,8 +33,8 @@ import { getSourceCode } from '../utils/get-source-code' import { toSingleLine } from '../utils/to-single-line' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' -import { useGroups } from '../utils/use-groups' import { makeFixes } from '../utils/make-fixes' +import { useGroups } from '../utils/use-groups' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' @@ -351,22 +350,14 @@ export let sortUnionOrIntersectionTypes = ({ for (let messageId of messageIds) { context.report({ - fix: fixer => [ - ...makeFixes({ - sortedNodes: sortedNodesExcludingEslintDisabled, - sourceCode, - options, - fixer, - nodes, - }), - ...makeNewlinesFixes({ + fix: fixer => + makeFixes({ sortedNodes: sortedNodesExcludingEslintDisabled, sourceCode, options, fixer, nodes, }), - ], data: { right: toSingleLine(right.name), left: toSingleLine(left.name), diff --git a/rules/sort-variable-declarations.ts b/rules/sort-variable-declarations.ts index 99f803fd9..90a6fb38b 100644 --- a/rules/sort-variable-declarations.ts +++ b/rules/sort-variable-declarations.ts @@ -29,8 +29,8 @@ import { toSingleLine } from '../utils/to-single-line' import { rangeToDiff } from '../utils/range-to-diff' import { getSettings } from '../utils/get-settings' import { isSortable } from '../utils/is-sortable' -import { sortNodes } from '../utils/sort-nodes' import { makeFixes } from '../utils/make-fixes' +import { sortNodes } from '../utils/sort-nodes' import { complete } from '../utils/complete' import { pairwise } from '../utils/pairwise' diff --git a/test/rules/sort-classes.test.ts b/test/rules/sort-classes.test.ts index 387900b31..dfecb75eb 100644 --- a/test/rules/sort-classes.test.ts +++ b/test/rules/sort-classes.test.ts @@ -4513,6 +4513,61 @@ describe(ruleName, () => { valid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): handles newlines and comment after fixes`, + rule, + { + invalid: [ + { + output: [ + dedent` + class Class { + a // Comment after + b() {} + + c() {} + } + `, + dedent` + class Class { + a // Comment after + + b() {} + c() {} + } + `, + ], + errors: [ + { + data: { + rightGroup: 'property', + leftGroup: 'method', + right: 'a', + left: 'b', + }, + messageId: 'unexpectedClassesGroupOrder', + }, + ], + code: dedent` + class Class { + b() {} + a // Comment after + + c() {} + } + `, + options: [ + { + groups: ['property', 'method'], + newlinesBetween: 'always', + }, + ], + }, + ], + valid: [], + }, + ) }) describe(`${ruleName}(${type}): sorts inline elements correctly`, () => { diff --git a/test/rules/sort-imports.test.ts b/test/rules/sort-imports.test.ts index 65c77f89f..4eabd2da0 100644 --- a/test/rules/sort-imports.test.ts +++ b/test/rules/sort-imports.test.ts @@ -2115,6 +2115,55 @@ describe(ruleName, () => { valid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): handles newlines and comment after fixes`, + rule, + { + invalid: [ + { + output: [ + dedent` + import { a } from './a' // Comment after + import { b } from 'b' + + import { c } from 'c' + `, + dedent` + import { a } from './a' // Comment after + + import { b } from 'b' + import { c } from 'c' + `, + ], + errors: [ + { + data: { + rightGroup: 'unknown', + leftGroup: 'external', + right: './a', + left: 'b', + }, + messageId: 'unexpectedImportsGroupOrder', + }, + ], + code: dedent` + import { b } from 'b' + import { a } from './a' // Comment after + + import { c } from 'c' + `, + options: [ + { + groups: ['unknown', 'external'], + newlinesBetween: 'always', + }, + ], + }, + ], + valid: [], + }, + ) }) ruleTester.run( diff --git a/test/rules/sort-interfaces.test.ts b/test/rules/sort-interfaces.test.ts index 363b0cff7..2fda71d19 100644 --- a/test/rules/sort-interfaces.test.ts +++ b/test/rules/sort-interfaces.test.ts @@ -2390,6 +2390,61 @@ describe(ruleName, () => { valid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): handles newlines and comment after fixes`, + rule, + { + invalid: [ + { + output: [ + dedent` + interface Interface { + a: string // Comment after + b: () => void + + c: () => void + }; + `, + dedent` + interface Interface { + a: string // Comment after + + b: () => void + c: () => void + }; + `, + ], + errors: [ + { + data: { + rightGroup: 'property', + leftGroup: 'method', + right: 'a', + left: 'b', + }, + messageId: 'unexpectedInterfacePropertiesGroupOrder', + }, + ], + code: dedent` + interface Interface { + b: () => void + a: string // Comment after + + c: () => void + }; + `, + options: [ + { + groups: ['property', 'method'], + newlinesBetween: 'always', + }, + ], + }, + ], + valid: [], + }, + ) }) ruleTester.run( diff --git a/test/rules/sort-intersection-types.test.ts b/test/rules/sort-intersection-types.test.ts index 24877ef85..66ba6aeca 100644 --- a/test/rules/sort-intersection-types.test.ts +++ b/test/rules/sort-intersection-types.test.ts @@ -1198,6 +1198,58 @@ describe(ruleName, () => { valid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): handles newlines and comment after fixes`, + rule, + { + invalid: [ + { + output: [ + dedent` + type T = + & 'a' // Comment after + & B + + & C + `, + dedent` + type T = + & 'a' // Comment after + + & B + & C + `, + ], + errors: [ + { + data: { + rightGroup: 'literal', + leftGroup: 'named', + right: "'a'", + left: 'B', + }, + messageId: 'unexpectedIntersectionTypesGroupOrder', + }, + ], + options: [ + { + groups: ['literal', 'named'], + newlinesBetween: 'always', + }, + ], + code: dedent` + type T = + & B + & 'a' // Comment after + + & C + `, + }, + ], + valid: [], + }, + ) }) ruleTester.run( diff --git a/test/rules/sort-modules.test.ts b/test/rules/sort-modules.test.ts index 237d322fd..c376e374a 100644 --- a/test/rules/sort-modules.test.ts +++ b/test/rules/sort-modules.test.ts @@ -2187,6 +2187,55 @@ describe(ruleName, () => { valid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): handles newlines and comment after fixes`, + rule, + { + invalid: [ + { + output: [ + dedent` + function a() {} // Comment after + type B = string + + type C = string + `, + dedent` + function a() {} // Comment after + + type B = string + type C = string + `, + ], + errors: [ + { + data: { + rightGroup: 'function', + leftGroup: 'type', + right: 'a', + left: 'B', + }, + messageId: 'unexpectedModulesGroupOrder', + }, + ], + options: [ + { + groups: ['function', 'type'], + newlinesBetween: 'always', + }, + ], + code: dedent` + type B = string + function a() {} // Comment after + + type C = string + `, + }, + ], + valid: [], + }, + ) }) describe(`${ruleName}(${type}): sorts inline elements correctly`, () => { diff --git a/test/rules/sort-object-types.test.ts b/test/rules/sort-object-types.test.ts index 04b6a990b..fae3833d5 100644 --- a/test/rules/sort-object-types.test.ts +++ b/test/rules/sort-object-types.test.ts @@ -2186,6 +2186,61 @@ describe(ruleName, () => { valid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): handles newlines and comment after fixes`, + rule, + { + invalid: [ + { + output: [ + dedent` + type Test = { + a: string // Comment after + b: () => void + + c: () => void + }; + `, + dedent` + type Test = { + a: string // Comment after + + b: () => void + c: () => void + }; + `, + ], + errors: [ + { + data: { + rightGroup: 'property', + leftGroup: 'method', + right: 'a', + left: 'b', + }, + messageId: 'unexpectedObjectTypesGroupOrder', + }, + ], + code: dedent` + type Test = { + b: () => void + a: string // Comment after + + c: () => void + }; + `, + options: [ + { + groups: ['property', 'method'], + newlinesBetween: 'always', + }, + ], + }, + ], + valid: [], + }, + ) }) ruleTester.run( diff --git a/test/rules/sort-objects.test.ts b/test/rules/sort-objects.test.ts index 4f878dd65..933c11c0d 100644 --- a/test/rules/sort-objects.test.ts +++ b/test/rules/sort-objects.test.ts @@ -2263,6 +2263,61 @@ describe(ruleName, () => { valid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): handles newlines and comment after fixes`, + rule, + { + invalid: [ + { + output: [ + dedent` + let obj = { + a, // Comment after + b() {}, + + c() {}, + }; + `, + dedent` + let obj = { + a, // Comment after + + b() {}, + c() {}, + }; + `, + ], + errors: [ + { + data: { + rightGroup: 'unknown', + leftGroup: 'method', + right: 'a', + left: 'b', + }, + messageId: 'unexpectedObjectsGroupOrder', + }, + ], + code: dedent` + let obj = { + b() {}, + a, // Comment after + + c() {}, + }; + `, + options: [ + { + groups: ['unknown', 'method'], + newlinesBetween: 'always', + }, + ], + }, + ], + valid: [], + }, + ) }) ruleTester.run( diff --git a/test/rules/sort-union-types.test.ts b/test/rules/sort-union-types.test.ts index 39cb313c2..08166f73e 100644 --- a/test/rules/sort-union-types.test.ts +++ b/test/rules/sort-union-types.test.ts @@ -1201,6 +1201,58 @@ describe(ruleName, () => { valid: [], }, ) + + ruleTester.run( + `${ruleName}(${type}): handles newlines and comment after fixes`, + rule, + { + invalid: [ + { + output: [ + dedent` + type T = + | 'a' // Comment after + | B + + | C + `, + dedent` + type T = + | 'a' // Comment after + + | B + | C + `, + ], + errors: [ + { + data: { + rightGroup: 'literal', + leftGroup: 'named', + right: "'a'", + left: 'B', + }, + messageId: 'unexpectedUnionTypesGroupOrder', + }, + ], + options: [ + { + groups: ['literal', 'named'], + newlinesBetween: 'always', + }, + ], + code: dedent` + type T = + | B + | 'a' // Comment after + + | C + `, + }, + ], + valid: [], + }, + ) }) ruleTester.run( diff --git a/utils/get-node-range.ts b/utils/get-node-range.ts index 78d0c798e..fb9398055 100644 --- a/utils/get-node-range.ts +++ b/utils/get-node-range.ts @@ -9,7 +9,7 @@ import { getCommentsBefore } from './get-comments-before' interface GetNodeRangeParameters { options?: { - partitionByComment: + partitionByComment?: | { block?: string[] | boolean | string line?: string[] | boolean | string diff --git a/utils/make-comment-after-fixes.ts b/utils/make-comment-after-fixes.ts index 26b921296..ee298963e 100644 --- a/utils/make-comment-after-fixes.ts +++ b/utils/make-comment-after-fixes.ts @@ -1,46 +1,42 @@ import type { TSESLint } from '@typescript-eslint/utils' -import type { TSESTree } from '@typescript-eslint/types' -import { getCommentAfter } from './get-comment-after' +import type { SortingNode } from '../types/sorting-node' -interface CommentAfterFixesParameters { - sortedNode: TSESTree.Token | TSESTree.Node - node: TSESTree.Token | TSESTree.Node +import { makeSingleNodeCommentAfterFixes } from './make-single-node-comment-after-fixes' + +interface MakeCommentAfterFixesParameters { sourceCode: TSESLint.SourceCode + sortedNodes: SortingNode[] fixer: TSESLint.RuleFixer + nodes: SortingNode[] } export let makeCommentAfterFixes = ({ - sortedNode, + sortedNodes, sourceCode, fixer, - node, -}: CommentAfterFixesParameters): TSESLint.RuleFix[] => { - let commentAfter = getCommentAfter(sortedNode, sourceCode) - let areNodesOnSameLine = node.loc.start.line === sortedNode.loc.end.line - if (!commentAfter || areNodesOnSameLine) { - return [] - } - + nodes, +}: MakeCommentAfterFixesParameters): TSESLint.RuleFix[] => { let fixes: TSESLint.RuleFix[] = [] - let tokenBefore = sourceCode.getTokenBefore(commentAfter) - - let range: TSESTree.Range = [ - tokenBefore!.range.at(1)!, - commentAfter.range.at(1)!, - ] - - fixes.push(fixer.replaceTextRange(range, '')) - - let tokenAfterNode = sourceCode.getTokenAfter(node) - fixes.push( - fixer.insertTextAfter( - tokenAfterNode?.loc.end.line === node.loc.end.line - ? tokenAfterNode - : node, - sourceCode.text.slice(...range), - ), - ) - + for (let max = nodes.length, i = 0; i < max; i++) { + let sortingNode = nodes.at(i)! + let sortedSortingNode = sortedNodes.at(i)! + let { node } = sortingNode + let { node: sortedNode } = sortedSortingNode + + if (node === sortedNode) { + continue + } + + fixes = [ + ...fixes, + ...makeSingleNodeCommentAfterFixes({ + sortedNode, + sourceCode, + fixer, + node, + }), + ] + } return fixes } diff --git a/utils/make-fixes.ts b/utils/make-fixes.ts index e7d440551..4340a87d6 100644 --- a/utils/make-fixes.ts +++ b/utils/make-fixes.ts @@ -3,11 +3,12 @@ import type { TSESLint } from '@typescript-eslint/utils' import type { SortingNode } from '../types/sorting-node' import { makeCommentAfterFixes } from './make-comment-after-fixes' -import { getNodeRange } from './get-node-range' +import { makeNewlinesFixes } from './make-newlines-fixes' +import { makeOrderFixes } from './make-order-fixes' interface MakeFixesParameters { options?: { - partitionByComment: + partitionByComment?: | { block?: string[] | boolean | string line?: string[] | boolean | string @@ -15,6 +16,13 @@ interface MakeFixesParameters { | string[] | boolean | string + groups?: ( + | { newlinesBetween: 'ignore' | 'always' | 'never' } + | string[] + | string + )[] + customGroups?: Record | CustomGroup[] + newlinesBetween?: 'ignore' | 'always' | 'never' } ignoreFirstNodeHighestBlockComment?: boolean sourceCode: TSESLint.SourceCode @@ -23,6 +31,11 @@ interface MakeFixesParameters { nodes: SortingNode[] } +interface CustomGroup { + newlinesInside?: 'always' | 'never' + groupName: string +} + export let makeFixes = ({ ignoreFirstNodeHighestBlockComment, sortedNodes, @@ -31,72 +44,40 @@ export let makeFixes = ({ fixer, nodes, }: MakeFixesParameters): TSESLint.RuleFix[] => { - let fixes: TSESLint.RuleFix[] = [] + let orderFixes = makeOrderFixes({ + ignoreFirstNodeHighestBlockComment, + sortedNodes, + sourceCode, + options, + nodes, + fixer, + }) - for (let max = nodes.length, i = 0; i < max; i++) { - let sortingNode = nodes.at(i)! - let sortedSortingNode = sortedNodes.at(i)! - let { node } = sortingNode - let { addSafetySemicolonWhenInline, node: sortedNode } = sortedSortingNode - let isNodeFirstNode = node === nodes.at(0)!.node - let isSortedNodeFirstNode = sortedNode === nodes.at(0)!.node - - if (node === sortedNode) { - continue - } - - let sortedNodeCode = sourceCode.text.slice( - ...getNodeRange({ - ignoreHighestBlockComment: - ignoreFirstNodeHighestBlockComment && isSortedNodeFirstNode, - node: sortedNode, - sourceCode, - options, - }), - ) - let sortedNodeText = sourceCode.getText(sortedNode) - let tokensAfter = sourceCode.getTokensAfter(node, { - includeComments: false, - count: 1, - }) - let nextToken = tokensAfter.at(0) - - let sortedNextNodeEndsWithSafeCharacter = - sortedNodeText.endsWith(';') || sortedNodeText.endsWith(',') - let isNextTokenOnSameLineAsNode = - nextToken?.loc.start.line === node.loc.end.line - let isNextTokenSafeCharacter = - nextToken?.value === ';' || nextToken?.value === ',' - if ( - addSafetySemicolonWhenInline && - isNextTokenOnSameLineAsNode && - !sortedNextNodeEndsWithSafeCharacter && - !isNextTokenSafeCharacter - ) { - sortedNodeCode += ';' - } - fixes.push( - fixer.replaceTextRange( - getNodeRange({ - ignoreHighestBlockComment: - ignoreFirstNodeHighestBlockComment && isNodeFirstNode, - sourceCode, - options, - node, - }), - sortedNodeCode, - ), - ) - fixes = [ - ...fixes, - ...makeCommentAfterFixes({ - sortedNode, - sourceCode, - fixer, - node, - }), - ] + let commentAfterFixes = makeCommentAfterFixes({ + sortedNodes, + sourceCode, + nodes, + fixer, + }) + if ( + commentAfterFixes.length || + !options?.groups || + !options.newlinesBetween + ) { + return [...orderFixes, ...commentAfterFixes] } - return fixes + let newlinesFixes = makeNewlinesFixes({ + options: { + ...options, + newlinesBetween: options.newlinesBetween, + groups: options.groups, + }, + sortedNodes, + sourceCode, + fixer, + nodes, + }) + + return [...orderFixes, ...newlinesFixes] } diff --git a/utils/make-order-fixes.ts b/utils/make-order-fixes.ts new file mode 100644 index 000000000..d956b87c5 --- /dev/null +++ b/utils/make-order-fixes.ts @@ -0,0 +1,92 @@ +import type { TSESLint } from '@typescript-eslint/utils' + +import type { SortingNode } from '../types/sorting-node' + +import { getNodeRange } from './get-node-range' + +interface MakeOrderFixesParameters { + options?: { + partitionByComment?: + | { + block?: string[] | boolean | string + line?: string[] | boolean | string + } + | string[] + | boolean + | string + } + ignoreFirstNodeHighestBlockComment?: boolean + sourceCode: TSESLint.SourceCode + sortedNodes: SortingNode[] + fixer: TSESLint.RuleFixer + nodes: SortingNode[] +} + +export let makeOrderFixes = ({ + ignoreFirstNodeHighestBlockComment, + sortedNodes, + sourceCode, + options, + fixer, + nodes, +}: MakeOrderFixesParameters): TSESLint.RuleFix[] => { + let fixes: TSESLint.RuleFix[] = [] + + for (let max = nodes.length, i = 0; i < max; i++) { + let sortingNode = nodes.at(i)! + let sortedSortingNode = sortedNodes.at(i)! + let { node } = sortingNode + let { addSafetySemicolonWhenInline, node: sortedNode } = sortedSortingNode + let isNodeFirstNode = node === nodes.at(0)!.node + let isSortedNodeFirstNode = sortedNode === nodes.at(0)!.node + + if (node === sortedNode) { + continue + } + + let sortedNodeCode = sourceCode.text.slice( + ...getNodeRange({ + ignoreHighestBlockComment: + ignoreFirstNodeHighestBlockComment && isSortedNodeFirstNode, + node: sortedNode, + sourceCode, + options, + }), + ) + let sortedNodeText = sourceCode.getText(sortedNode) + let tokensAfter = sourceCode.getTokensAfter(node, { + includeComments: false, + count: 1, + }) + let nextToken = tokensAfter.at(0) + + let sortedNextNodeEndsWithSafeCharacter = + sortedNodeText.endsWith(';') || sortedNodeText.endsWith(',') + let isNextTokenOnSameLineAsNode = + nextToken?.loc.start.line === node.loc.end.line + let isNextTokenSafeCharacter = + nextToken?.value === ';' || nextToken?.value === ',' + if ( + addSafetySemicolonWhenInline && + isNextTokenOnSameLineAsNode && + !sortedNextNodeEndsWithSafeCharacter && + !isNextTokenSafeCharacter + ) { + sortedNodeCode += ';' + } + fixes.push( + fixer.replaceTextRange( + getNodeRange({ + ignoreHighestBlockComment: + ignoreFirstNodeHighestBlockComment && isNodeFirstNode, + sourceCode, + options, + node, + }), + sortedNodeCode, + ), + ) + } + + return fixes +} diff --git a/utils/make-single-node-comment-after-fixes.ts b/utils/make-single-node-comment-after-fixes.ts new file mode 100644 index 000000000..3a44959bb --- /dev/null +++ b/utils/make-single-node-comment-after-fixes.ts @@ -0,0 +1,46 @@ +import type { TSESLint } from '@typescript-eslint/utils' +import type { TSESTree } from '@typescript-eslint/types' + +import { getCommentAfter } from './get-comment-after' + +interface MakeSingleNodeCommentAfterFixesParameters { + sortedNode: TSESTree.Token | TSESTree.Node + node: TSESTree.Token | TSESTree.Node + sourceCode: TSESLint.SourceCode + fixer: TSESLint.RuleFixer +} + +export let makeSingleNodeCommentAfterFixes = ({ + sortedNode, + sourceCode, + fixer, + node, +}: MakeSingleNodeCommentAfterFixesParameters): TSESLint.RuleFix[] => { + let commentAfter = getCommentAfter(sortedNode, sourceCode) + let areNodesOnSameLine = node.loc.start.line === sortedNode.loc.end.line + if (!commentAfter || areNodesOnSameLine) { + return [] + } + + let fixes: TSESLint.RuleFix[] = [] + let tokenBefore = sourceCode.getTokenBefore(commentAfter) + + let range: TSESTree.Range = [ + tokenBefore!.range.at(1)!, + commentAfter.range.at(1)!, + ] + + fixes.push(fixer.replaceTextRange(range, '')) + + let tokenAfterNode = sourceCode.getTokenAfter(node) + fixes.push( + fixer.insertTextAfter( + tokenAfterNode?.loc.end.line === node.loc.end.line + ? tokenAfterNode + : node, + sourceCode.text.slice(...range), + ), + ) + + return fixes +}