From 90476de1178213953adc9c0b47d08d79972968f4 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 25 Oct 2022 10:14:34 -0400 Subject: [PATCH 1/4] feat(eslint-plugin) [sort-type-union-intersection-members] rename to sort-type-constituents --- .eslintrc.js | 2 +- ...tersection-members.md => sort-type-constituents.md} | 6 +++--- packages/eslint-plugin/src/configs/all.ts | 2 +- packages/eslint-plugin/src/rules/index.ts | 4 ++-- ...tersection-members.ts => sort-type-constituents.ts} | 10 +++++----- ...mbers.test.ts => sort-type-constituents.ts.test.ts} | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) rename packages/eslint-plugin/docs/rules/{sort-type-union-intersection-members.md => sort-type-constituents.md} (86%) rename packages/eslint-plugin/src/rules/{sort-type-union-intersection-members.ts => sort-type-constituents.ts} (95%) rename packages/eslint-plugin/tests/rules/{sort-type-union-intersection-members.test.ts => sort-type-constituents.ts.test.ts} (97%) diff --git a/.eslintrc.js b/.eslintrc.js index 8e62b6d06e39..da7ff36fd3b2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -308,7 +308,7 @@ module.exports = { rules: { // disallow ALL unused vars '@typescript-eslint/no-unused-vars': 'error', - '@typescript-eslint/sort-type-union-intersection-members': 'error', + '@typescript-eslint/sort-type-constituents': 'error', }, }, { diff --git a/packages/eslint-plugin/docs/rules/sort-type-union-intersection-members.md b/packages/eslint-plugin/docs/rules/sort-type-constituents.md similarity index 86% rename from packages/eslint-plugin/docs/rules/sort-type-union-intersection-members.md rename to packages/eslint-plugin/docs/rules/sort-type-constituents.md index 2a47547219bf..264ef2b52df9 100644 --- a/packages/eslint-plugin/docs/rules/sort-type-union-intersection-members.md +++ b/packages/eslint-plugin/docs/rules/sort-type-constituents.md @@ -1,10 +1,10 @@ --- -description: 'Enforce members of a type union/intersection to be sorted alphabetically.' +description: 'Enforce constituents of a type union/intersection to be sorted alphabetically.' --- > 🛑 This file is source code, not the primary documentation location! 🛑 > -> See **https://typescript-eslint.io/rules/sort-type-union-intersection-members** for documentation. +> See **https://typescript-eslint.io/rules/sort-type-constituents** for documentation. Sorting union (`|`) and intersection (`&`) types can help: @@ -84,7 +84,7 @@ type T4 = ### `groupOrder` -Each member of the type is placed into a group, and then the rule sorts alphabetically within each group. +Each constituent of the type is placed into a group, and then the rule sorts alphabetically within each group. The ordering of groups is determined by this option. - `conditional` - Conditional types (`A extends B ? C : D`) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 1f6530ead3ec..20ea892f581d 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -154,7 +154,7 @@ export = { '@typescript-eslint/return-await': 'error', semi: 'off', '@typescript-eslint/semi': 'error', - '@typescript-eslint/sort-type-union-intersection-members': 'error', + '@typescript-eslint/sort-type-constituents': 'error', 'space-before-blocks': 'off', '@typescript-eslint/space-before-blocks': 'error', 'space-before-function-paren': 'off', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 851661400fbb..c08edff75362 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -115,7 +115,7 @@ import restrictPlusOperands from './restrict-plus-operands'; import restrictTemplateExpressions from './restrict-template-expressions'; import returnAwait from './return-await'; import semi from './semi'; -import sortTypeUnionIntersectionMembers from './sort-type-union-intersection-members'; +import sortTypeConstituents from './sort-type-constituents'; import spaceBeforeBlocks from './space-before-blocks'; import spaceBeforeFunctionParen from './space-before-function-paren'; import spaceInfixOps from './space-infix-ops'; @@ -245,7 +245,7 @@ export default { 'restrict-template-expressions': restrictTemplateExpressions, 'return-await': returnAwait, semi: semi, - 'sort-type-union-intersection-members': sortTypeUnionIntersectionMembers, + 'sort-type-constituents': sortTypeConstituents, 'space-before-blocks': spaceBeforeBlocks, 'space-before-function-paren': spaceBeforeFunctionParen, 'space-infix-ops': spaceInfixOps, diff --git a/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts b/packages/eslint-plugin/src/rules/sort-type-constituents.ts similarity index 95% rename from packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts rename to packages/eslint-plugin/src/rules/sort-type-constituents.ts index ded7212c6c95..92b5e44c6f98 100644 --- a/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts +++ b/packages/eslint-plugin/src/rules/sort-type-constituents.ts @@ -112,20 +112,20 @@ export type Options = [ export type MessageIds = 'notSorted' | 'notSortedNamed' | 'suggestFix'; export default util.createRule({ - name: 'sort-type-union-intersection-members', + name: 'sort-type-constituents', meta: { type: 'suggestion', docs: { description: - 'Enforce members of a type union/intersection to be sorted alphabetically', + 'Enforce constituents of a type union/intersection to be sorted alphabetically', recommended: false, }, fixable: 'code', hasSuggestions: true, messages: { - notSorted: '{{type}} type members must be sorted.', - notSortedNamed: '{{type}} type {{name}} members must be sorted.', - suggestFix: 'Sort members of type (removes all comments).', + notSorted: '{{type}} type constituents must be sorted.', + notSortedNamed: '{{type}} type {{name}} constituents must be sorted.', + suggestFix: 'Sort constituents of type (removes all comments).', }, schema: [ { diff --git a/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts b/packages/eslint-plugin/tests/rules/sort-type-constituents.ts.test.ts similarity index 97% rename from packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts rename to packages/eslint-plugin/tests/rules/sort-type-constituents.ts.test.ts index 38ae67f18043..b9989c2cf875 100644 --- a/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts +++ b/packages/eslint-plugin/tests/rules/sort-type-constituents.ts.test.ts @@ -3,8 +3,8 @@ import type { TSESLint } from '@typescript-eslint/utils'; import type { MessageIds, Options, -} from '../../src/rules/sort-type-union-intersection-members'; -import rule from '../../src/rules/sort-type-union-intersection-members'; +} from '../../src/rules/sort-type-constituents'; +import rule from '../../src/rules/sort-type-constituents'; import { noFormat, RuleTester } from '../RuleTester'; const ruleTester = new RuleTester({ @@ -286,7 +286,7 @@ type T = ]; }; -ruleTester.run('sort-type-union-intersection-members', rule, { +ruleTester.run('sort-type-constituents', rule, { valid: [ ...valid('|'), { From a439f58f1b96323a174284fa60a4c79caa1356a3 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 25 Oct 2022 10:31:25 -0400 Subject: [PATCH 2/4] Sigh, test rename --- ...ype-constituents.ts.test.ts => sort-type-constituents.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/eslint-plugin/tests/rules/{sort-type-constituents.ts.test.ts => sort-type-constituents.test.ts} (100%) diff --git a/packages/eslint-plugin/tests/rules/sort-type-constituents.ts.test.ts b/packages/eslint-plugin/tests/rules/sort-type-constituents.test.ts similarity index 100% rename from packages/eslint-plugin/tests/rules/sort-type-constituents.ts.test.ts rename to packages/eslint-plugin/tests/rules/sort-type-constituents.test.ts From f0969a6bf1761134d9177ac5126884d992f27c62 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 26 Oct 2022 17:42:27 -0400 Subject: [PATCH 3/4] Added back rule, with replacedBy --- .../sort-type-union-intersection-members.md | 106 ++++++ .../sort-type-union-intersection-members.ts | 271 ++++++++++++++ ...rt-type-union-intersection-members.test.ts | 334 ++++++++++++++++++ 3 files changed, 711 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/sort-type-union-intersection-members.md create mode 100644 packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts create mode 100644 packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts diff --git a/packages/eslint-plugin/docs/rules/sort-type-union-intersection-members.md b/packages/eslint-plugin/docs/rules/sort-type-union-intersection-members.md new file mode 100644 index 000000000000..dbaf1807edff --- /dev/null +++ b/packages/eslint-plugin/docs/rules/sort-type-union-intersection-members.md @@ -0,0 +1,106 @@ +--- +description: 'Enforce members of a type union/intersection to be sorted alphabetically.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/sort-type-union-intersection-members** for documentation. + +:::danger Deprecated + +This rule has been renamed to [`sort-type-union-intersection-members`](./sort-type-union-intersection-members.md). +::: + +Sorting union (`|`) and intersection (`&`) types can help: + +- keep your codebase standardized +- find repeated types +- reduce diff churn + +This rule reports on any types that aren't sorted alphabetically. + +> Types are sorted case-insensitively and treating numbers like a human would, falling back to character code sorting in case of ties. + +## Examples + + + +### ❌ Incorrect + +```ts +type T1 = B | A; + +type T2 = { b: string } & { a: string }; + +type T3 = [1, 2, 4] & [1, 2, 3]; + +type T4 = + | [1, 2, 4] + | [1, 2, 3] + | { b: string } + | { a: string } + | (() => void) + | (() => string) + | 'b' + | 'a' + | 'b' + | 'a' + | readonly string[] + | readonly number[] + | string[] + | number[] + | B + | A + | string + | any; +``` + +### ✅ Correct + +```ts +type T1 = A | B; + +type T2 = { a: string } & { b: string }; + +type T3 = [1, 2, 3] & [1, 2, 4]; + +type T4 = + | any + | string + | A + | B + | number[] + | string[] + | readonly number[] + | readonly string[] + | 'a' + | 'b' + | 'a' + | 'b' + | (() => string) + | (() => void) + | { a: string } + | { b: string } + | [1, 2, 3] + | [1, 2, 4]; +``` + +## Options + +### `groupOrder` + +Each member of the type is placed into a group, and then the rule sorts alphabetically within each group. +The ordering of groups is determined by this option. + +- `conditional` - Conditional types (`A extends B ? C : D`) +- `function` - Function and constructor types (`() => void`, `new () => type`) +- `import` - Import types (`import('path')`) +- `intersection` - Intersection types (`A & B`) +- `keyword` - Keyword types (`any`, `string`, etc) +- `literal` - Literal types (`1`, `'b'`, `true`, etc) +- `named` - Named types (`A`, `A['prop']`, `B[]`, `Array`) +- `object` - Object types (`{ a: string }`, `{ [key: string]: number }`) +- `operator` - Operator types (`keyof A`, `typeof B`, `readonly C[]`) +- `tuple` - Tuple types (`[A, B, C]`) +- `union` - Union types (`A | B`) +- `nullish` - `null` and `undefined` diff --git a/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts b/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts new file mode 100644 index 000000000000..2a83b4b0525b --- /dev/null +++ b/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts @@ -0,0 +1,271 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import * as util from '../util'; +import { getEnumNames } from '../util'; + +enum Group { + conditional = 'conditional', + function = 'function', + import = 'import', + intersection = 'intersection', + keyword = 'keyword', + nullish = 'nullish', + literal = 'literal', + named = 'named', + object = 'object', + operator = 'operator', + tuple = 'tuple', + union = 'union', +} + +function getGroup(node: TSESTree.TypeNode): Group { + switch (node.type) { + case AST_NODE_TYPES.TSConditionalType: + return Group.conditional; + + case AST_NODE_TYPES.TSConstructorType: + case AST_NODE_TYPES.TSFunctionType: + return Group.function; + + case AST_NODE_TYPES.TSImportType: + return Group.import; + + case AST_NODE_TYPES.TSIntersectionType: + return Group.intersection; + + case AST_NODE_TYPES.TSAnyKeyword: + case AST_NODE_TYPES.TSBigIntKeyword: + case AST_NODE_TYPES.TSBooleanKeyword: + case AST_NODE_TYPES.TSNeverKeyword: + case AST_NODE_TYPES.TSNumberKeyword: + case AST_NODE_TYPES.TSObjectKeyword: + case AST_NODE_TYPES.TSStringKeyword: + case AST_NODE_TYPES.TSSymbolKeyword: + case AST_NODE_TYPES.TSThisType: + case AST_NODE_TYPES.TSUnknownKeyword: + case AST_NODE_TYPES.TSIntrinsicKeyword: + return Group.keyword; + + case AST_NODE_TYPES.TSNullKeyword: + case AST_NODE_TYPES.TSUndefinedKeyword: + case AST_NODE_TYPES.TSVoidKeyword: + return Group.nullish; + + case AST_NODE_TYPES.TSLiteralType: + case AST_NODE_TYPES.TSTemplateLiteralType: + return Group.literal; + + case AST_NODE_TYPES.TSArrayType: + case AST_NODE_TYPES.TSIndexedAccessType: + case AST_NODE_TYPES.TSInferType: + case AST_NODE_TYPES.TSTypeReference: + return Group.named; + + case AST_NODE_TYPES.TSMappedType: + case AST_NODE_TYPES.TSTypeLiteral: + return Group.object; + + case AST_NODE_TYPES.TSTypeOperator: + case AST_NODE_TYPES.TSTypeQuery: + return Group.operator; + + case AST_NODE_TYPES.TSTupleType: + return Group.tuple; + + case AST_NODE_TYPES.TSUnionType: + return Group.union; + + // These types should never occur as part of a union/intersection + case AST_NODE_TYPES.TSAbstractKeyword: + case AST_NODE_TYPES.TSAsyncKeyword: + case AST_NODE_TYPES.TSDeclareKeyword: + case AST_NODE_TYPES.TSExportKeyword: + case AST_NODE_TYPES.TSNamedTupleMember: + case AST_NODE_TYPES.TSOptionalType: + case AST_NODE_TYPES.TSPrivateKeyword: + case AST_NODE_TYPES.TSProtectedKeyword: + case AST_NODE_TYPES.TSPublicKeyword: + case AST_NODE_TYPES.TSReadonlyKeyword: + case AST_NODE_TYPES.TSRestType: + case AST_NODE_TYPES.TSStaticKeyword: + case AST_NODE_TYPES.TSTypePredicate: + /* istanbul ignore next */ + throw new Error(`Unexpected Type ${node.type}`); + } +} + +function requiresParentheses(node: TSESTree.TypeNode): boolean { + return ( + node.type === AST_NODE_TYPES.TSFunctionType || + node.type === AST_NODE_TYPES.TSConstructorType + ); +} + +export type Options = [ + { + checkIntersections?: boolean; + checkUnions?: boolean; + groupOrder?: string[]; + }, +]; +export type MessageIds = 'notSorted' | 'notSortedNamed' | 'suggestFix'; + +export default util.createRule({ + name: 'sort-type-union-intersection-members', + meta: { + deprecated: true, + type: 'suggestion', + docs: { + description: + 'Enforce members of a type union/intersection to be sorted alphabetically', + recommended: false, + }, + fixable: 'code', + hasSuggestions: true, + messages: { + notSorted: '{{type}} type members must be sorted.', + notSortedNamed: '{{type}} type {{name}} members must be sorted.', + suggestFix: 'Sort members of type (removes all comments).', + }, + replacedBy: ['@typescript-eslint/sort-type-constituents'], + schema: [ + { + type: 'object', + properties: { + checkIntersections: { + description: 'Whether to check intersection types.', + type: 'boolean', + }, + checkUnions: { + description: 'Whether to check union types.', + type: 'boolean', + }, + groupOrder: { + description: 'Ordering of the groups.', + type: 'array', + items: { + type: 'string', + enum: getEnumNames(Group), + }, + }, + }, + }, + ], + }, + defaultOptions: [ + { + checkIntersections: true, + checkUnions: true, + groupOrder: [ + Group.named, + Group.keyword, + Group.operator, + Group.literal, + Group.function, + Group.import, + Group.conditional, + Group.object, + Group.tuple, + Group.intersection, + Group.union, + Group.nullish, + ], + }, + ], + create(context, [{ checkIntersections, checkUnions, groupOrder }]) { + const sourceCode = context.getSourceCode(); + + const collator = new Intl.Collator('en', { + sensitivity: 'base', + numeric: true, + }); + + function checkSorting( + node: TSESTree.TSIntersectionType | TSESTree.TSUnionType, + ): void { + const sourceOrder = node.types.map(type => { + const group = groupOrder?.indexOf(getGroup(type)) ?? -1; + return { + group: group === -1 ? Number.MAX_SAFE_INTEGER : group, + node: type, + text: sourceCode.getText(type), + }; + }); + const expectedOrder = [...sourceOrder].sort((a, b) => { + if (a.group !== b.group) { + return a.group - b.group; + } + + return ( + collator.compare(a.text, b.text) || + (a.text < b.text ? -1 : a.text > b.text ? 1 : 0) + ); + }); + + const hasComments = node.types.some(type => { + const count = + sourceCode.getCommentsBefore(type).length + + sourceCode.getCommentsAfter(type).length; + return count > 0; + }); + + for (let i = 0; i < expectedOrder.length; i += 1) { + if (expectedOrder[i].node !== sourceOrder[i].node) { + let messageId: MessageIds = 'notSorted'; + const data = { + name: '', + type: + node.type === AST_NODE_TYPES.TSIntersectionType + ? 'Intersection' + : 'Union', + }; + if (node.parent?.type === AST_NODE_TYPES.TSTypeAliasDeclaration) { + messageId = 'notSortedNamed'; + data.name = node.parent.id.name; + } + + const fix: TSESLint.ReportFixFunction = fixer => { + const sorted = expectedOrder + .map(t => (requiresParentheses(t.node) ? `(${t.text})` : t.text)) + .join( + node.type === AST_NODE_TYPES.TSIntersectionType ? ' & ' : ' | ', + ); + + return fixer.replaceText(node, sorted); + }; + return context.report({ + node, + messageId, + data, + // don't autofix if any of the types have leading/trailing comments + // the logic for preserving them correctly is a pain - we may implement this later + ...(hasComments + ? { + suggest: [ + { + messageId: 'suggestFix', + fix, + }, + ], + } + : { fix }), + }); + } + } + } + + return { + ...(checkIntersections && { + TSIntersectionType(node): void { + checkSorting(node); + }, + }), + ...(checkUnions && { + TSUnionType(node): void { + checkSorting(node); + }, + }), + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts b/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts new file mode 100644 index 000000000000..38ae67f18043 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts @@ -0,0 +1,334 @@ +import type { TSESLint } from '@typescript-eslint/utils'; + +import type { + MessageIds, + Options, +} from '../../src/rules/sort-type-union-intersection-members'; +import rule from '../../src/rules/sort-type-union-intersection-members'; +import { noFormat, RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +const valid = (operator: '|' | '&'): TSESLint.ValidTestCase[] => [ + { + code: `type T = A ${operator} B;`, + }, + { + code: `type T = A ${operator} /* comment */ B;`, + }, + { + code: `type T = 'A' ${operator} 'B';`, + }, + { + code: `type T = 1 ${operator} 2;`, + }, + { + code: noFormat`type T = (A) ${operator} (B);`, + }, + { + code: `type T = { a: string } ${operator} { b: string };`, + }, + { + code: `type T = [1, 2, 3] ${operator} [1, 2, 4];`, + }, + { + code: `type T = (() => string) ${operator} (() => void);`, + }, + { + code: `type T = () => string ${operator} void;`, + }, + { + // testing the default ordering + code: noFormat` +type T = + ${operator} A + ${operator} B + ${operator} intrinsic + ${operator} number[] + ${operator} string[] + ${operator} any + ${operator} string + ${operator} symbol + ${operator} this + ${operator} readonly number[] + ${operator} readonly string[] + ${operator} 'a' + ${operator} 'b' + ${operator} "a" + ${operator} "b" + ${operator} (() => string) + ${operator} (() => void) + ${operator} (new () => string) + ${operator} (new () => void) + ${operator} import('bar') + ${operator} import('foo') + ${operator} (number extends string ? unknown : never) + ${operator} (string extends string ? unknown : never) + ${operator} { [a in string]: string } + ${operator} { [a: string]: string } + ${operator} { [b in string]: string } + ${operator} { [b: string]: string } + ${operator} { a: string } + ${operator} { b: string } + ${operator} [1, 2, 3] + ${operator} [1, 2, 4] + ${operator} (A & B) + ${operator} (B & C) + ${operator} (A | B) + ${operator} (B | C) + ${operator} null + ${operator} undefined + `, + }, +]; +const invalid = ( + operator: '|' | '&', +): TSESLint.InvalidTestCase[] => { + const type = operator === '|' ? 'Union' : 'Intersection'; + return [ + { + code: `type T = B ${operator} A;`, + output: `type T = A ${operator} B;`, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + }, + ], + }, + { + code: `type T = 'B' ${operator} 'A';`, + output: `type T = 'A' ${operator} 'B';`, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + }, + ], + }, + { + code: `type T = 2 ${operator} 1;`, + output: `type T = 1 ${operator} 2;`, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + }, + ], + }, + { + code: noFormat`type T = (B) ${operator} (A);`, + output: `type T = A ${operator} B;`, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + }, + ], + }, + { + code: `type T = { b: string } ${operator} { a: string };`, + output: `type T = { a: string } ${operator} { b: string };`, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + }, + ], + }, + { + code: `type T = [1, 2, 4] ${operator} [1, 2, 3];`, + output: `type T = [1, 2, 3] ${operator} [1, 2, 4];`, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + }, + ], + }, + { + code: `type T = (() => void) ${operator} (() => string);`, + output: `type T = (() => string) ${operator} (() => void);`, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + }, + ], + }, + { + code: `type T = () => void ${operator} string;`, + output: `type T = () => string ${operator} void;`, + errors: [ + { + messageId: 'notSorted', + data: { + type, + }, + }, + ], + }, + { + code: `type T = () => undefined ${operator} null;`, + output: `type T = () => null ${operator} undefined;`, + errors: [ + { + messageId: 'notSorted', + data: { + type, + }, + }, + ], + }, + { + code: noFormat` +type T = + ${operator} [1, 2, 4] + ${operator} [1, 2, 3] + ${operator} { b: string } + ${operator} { a: string } + ${operator} (() => void) + ${operator} (() => string) + ${operator} "b" + ${operator} "a" + ${operator} 'b' + ${operator} 'a' + ${operator} readonly string[] + ${operator} readonly number[] + ${operator} string[] + ${operator} number[] + ${operator} B + ${operator} A + ${operator} undefined + ${operator} null + ${operator} string + ${operator} any; + `, + output: ` +type T = + A ${operator} B ${operator} number[] ${operator} string[] ${operator} any ${operator} string ${operator} readonly number[] ${operator} readonly string[] ${operator} 'a' ${operator} 'b' ${operator} "a" ${operator} "b" ${operator} (() => string) ${operator} (() => void) ${operator} { a: string } ${operator} { b: string } ${operator} [1, 2, 3] ${operator} [1, 2, 4] ${operator} null ${operator} undefined; + `, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + }, + ], + }, + { + code: `type T = B ${operator} /* comment */ A;`, + output: null, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + suggestions: [ + { + messageId: 'suggestFix', + output: `type T = A ${operator} B;`, + }, + ], + }, + ], + }, + { + code: `type T = (() => /* comment */ A) ${operator} B;`, + output: `type T = B ${operator} (() => /* comment */ A);`, + errors: [ + { + messageId: 'notSortedNamed', + data: { + type, + name: 'T', + }, + suggestions: null, + }, + ], + }, + { + code: `type Expected = (new (x: number) => boolean) ${operator} string;`, + output: `type Expected = string ${operator} (new (x: number) => boolean);`, + errors: [ + { + messageId: 'notSortedNamed', + }, + ], + }, + ]; +}; + +ruleTester.run('sort-type-union-intersection-members', rule, { + valid: [ + ...valid('|'), + { + code: 'type T = B | A;', + options: [ + { + checkUnions: false, + }, + ], + }, + + ...valid('&'), + { + code: 'type T = B & A;', + options: [ + { + checkIntersections: false, + }, + ], + }, + + { + code: noFormat` +type T = [1] | 'a' | 'b' | "b" | 1 | 2 | {}; + `, + options: [ + { + groupOrder: ['tuple', 'literal', 'object'], + }, + ], + }, + { + // if not specified - groups should be placed last + code: ` +type T = 1 | string | {} | A; + `, + options: [ + { + groupOrder: ['literal', 'keyword'], + }, + ], + }, + ], + invalid: [...invalid('|'), ...invalid('&')], +}); From 3ae3756314f035a9d3a967ba9a698a2d8fd61018 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 26 Oct 2022 17:43:48 -0400 Subject: [PATCH 4/4] Fixed up rules exports --- packages/eslint-plugin/src/rules/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index c08edff75362..8a3c2bbf4371 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -116,6 +116,7 @@ import restrictTemplateExpressions from './restrict-template-expressions'; import returnAwait from './return-await'; import semi from './semi'; import sortTypeConstituents from './sort-type-constituents'; +import sortTypeUnionIntersectionMembers from './sort-type-union-intersection-members'; import spaceBeforeBlocks from './space-before-blocks'; import spaceBeforeFunctionParen from './space-before-function-paren'; import spaceInfixOps from './space-infix-ops'; @@ -246,6 +247,7 @@ export default { 'return-await': returnAwait, semi: semi, 'sort-type-constituents': sortTypeConstituents, + 'sort-type-union-intersection-members': sortTypeUnionIntersectionMembers, 'space-before-blocks': spaceBeforeBlocks, 'space-before-function-paren': spaceBeforeFunctionParen, 'space-infix-ops': spaceInfixOps, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy