diff --git a/packages/eslint-plugin/docs/rules/require-types-exports.mdx b/packages/eslint-plugin/docs/rules/require-types-exports.mdx new file mode 100644 index 000000000000..baa939fc2115 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/require-types-exports.mdx @@ -0,0 +1,80 @@ +--- +description: 'Require exporting types that are used in exported entities.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/require-types-exports** for documentation. + +When exporting entities from a module, it is recommended to export also all the +types that are used in their declarations. This is useful for consumers of the +module, as it allows them to use the types in their own code without having to +use utility types like [`Parameters`](https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype) +or [`ReturnType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype) +in order to extract the types from your code. + +## Examples + + + + +```ts +type Arg = string; +type Result = number; + +export function strLength(arg: Arg): Result { + return arg.length; +} + +interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; + +enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +``` + + + + +```ts +export type Arg = string; +export type Result = number; + +export function strLength(arg: Arg): Result { + return arg.length; +} + +export interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; + +export enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +``` + + + + +## When Not To Use It + +When you don't want to enforce exporting types that are used in exported functions declarations. diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index c8973db2dd8b..9e8de4b1c1f2 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -146,6 +146,7 @@ export = { '@typescript-eslint/require-array-sort-compare': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': 'error', '@typescript-eslint/restrict-template-expressions': 'error', 'no-return-await': 'off', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index cdce2e4962b6..cc55b1ab5c10 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -73,6 +73,7 @@ export = { '@typescript-eslint/prefer-return-this-type': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': [ 'error', { diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index 9c51d5c47348..cb825c95d3cb 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -40,6 +40,7 @@ export = { '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unified-signatures': 'error', }, diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index 26b5c270914e..ebc32526ee89 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -72,13 +72,13 @@ function typeNeedsParentheses(node: TSESTree.Node): boolean { } export type OptionString = 'array-simple' | 'array' | 'generic'; -type Options = [ +export type Options = [ { default: OptionString; readonly?: OptionString; }, ]; -type MessageIds = +export type MessageIds = | 'errorStringArray' | 'errorStringArraySimple' | 'errorStringGeneric' diff --git a/packages/eslint-plugin/src/rules/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index b554510f57d3..71ec5696f867 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -3,12 +3,12 @@ import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule, getStringLength, nullThrows } from '../util'; -type DirectiveConfig = +export type DirectiveConfig = | boolean | 'allow-with-description' | { descriptionFormat: string }; -interface Options { +export interface Options { 'ts-expect-error'?: DirectiveConfig; 'ts-ignore'?: DirectiveConfig; 'ts-nocheck'?: DirectiveConfig; @@ -18,7 +18,7 @@ interface Options { const defaultMinimumDescriptionLength = 3; -type MessageIds = +export type MessageIds = | 'tsDirectiveComment' | 'tsIgnoreInsteadOfExpectError' | 'tsDirectiveCommentDescriptionNotMatchPattern' diff --git a/packages/eslint-plugin/src/rules/class-literal-property-style.ts b/packages/eslint-plugin/src/rules/class-literal-property-style.ts index 6813e80f60d8..d8aa8d124fe5 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -9,8 +9,8 @@ import { nullThrows, } from '../util'; -type Options = ['fields' | 'getters']; -type MessageIds = +export type Options = ['fields' | 'getters']; +export type MessageIds = | 'preferFieldStyle' | 'preferFieldStyleSuggestion' | 'preferGetterStyle' diff --git a/packages/eslint-plugin/src/rules/class-methods-use-this.ts b/packages/eslint-plugin/src/rules/class-methods-use-this.ts index 6236a46ddb7b..793c8e8a5d90 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -8,7 +8,7 @@ import { getStaticStringValue, } from '../util'; -type Options = [ +export type Options = [ { exceptMethods?: string[]; enforceForClassFields?: boolean; @@ -16,7 +16,7 @@ type Options = [ ignoreClassesThatImplementAnInterface?: boolean | 'public-fields'; }, ]; -type MessageIds = 'missingThis'; +export type MessageIds = 'missingThis'; export default createRule({ name: 'class-methods-use-this', diff --git a/packages/eslint-plugin/src/rules/comma-spacing.ts b/packages/eslint-plugin/src/rules/comma-spacing.ts index cd283e4c97ac..79e9450c012b 100644 --- a/packages/eslint-plugin/src/rules/comma-spacing.ts +++ b/packages/eslint-plugin/src/rules/comma-spacing.ts @@ -10,13 +10,13 @@ import { isTokenOnSameLine, } from '../util'; -type Options = [ +export type Options = [ { before: boolean; after: boolean; }, ]; -type MessageIds = 'missing' | 'unexpected'; +export type MessageIds = 'missing' | 'unexpected'; export default createRule({ name: 'comma-spacing', diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index 166a2a8ecfda..466fc83a544a 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -3,8 +3,8 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, nullThrows, NullThrowsReasons } from '../util'; -type MessageIds = 'preferConstructor' | 'preferTypeAnnotation'; -type Options = ['constructor' | 'type-annotation']; +export type MessageIds = 'preferConstructor' | 'preferTypeAnnotation'; +export type Options = ['constructor' | 'type-annotation']; export default createRule({ name: 'consistent-generic-constructors', diff --git a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts index f0f91cc32b8e..c85f49f71bf3 100644 --- a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts +++ b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts @@ -3,8 +3,8 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type MessageIds = 'preferIndexSignature' | 'preferRecord'; -type Options = ['index-signature' | 'record']; +export type MessageIds = 'preferIndexSignature' | 'preferRecord'; +export type Options = ['index-signature' | 'record']; export default createRule({ name: 'consistent-indexed-object-style', diff --git a/packages/eslint-plugin/src/rules/consistent-return.ts b/packages/eslint-plugin/src/rules/consistent-return.ts index 5d4cc3fb9256..8abb899ef7ce 100644 --- a/packages/eslint-plugin/src/rules/consistent-return.ts +++ b/packages/eslint-plugin/src/rules/consistent-return.ts @@ -11,10 +11,10 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('consistent-return'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; -type FunctionNode = +export type FunctionNode = | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression; diff --git a/packages/eslint-plugin/src/rules/consistent-type-exports.ts b/packages/eslint-plugin/src/rules/consistent-type-exports.ts index 236659d13adb..cbd3b3236303 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-exports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-exports.ts @@ -12,7 +12,7 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { fixMixedExportsWithInlineTypeSpecifier: boolean; }, @@ -32,7 +32,7 @@ interface ReportValueExport { inlineTypeSpecifiers: TSESTree.ExportSpecifier[]; } -type MessageIds = +export type MessageIds = | 'multipleExportsAreTypes' | 'singleExportIsType' | 'typeOverValue'; diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index f4e98d2d9e00..3dfcb8ced4e0 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -15,10 +15,10 @@ import { NullThrowsReasons, } from '../util'; -type Prefer = 'no-type-imports' | 'type-imports'; -type FixStyle = 'inline-type-imports' | 'separate-type-imports'; +export type Prefer = 'no-type-imports' | 'type-imports'; +export type FixStyle = 'inline-type-imports' | 'separate-type-imports'; -type Options = [ +export type Options = [ { prefer?: Prefer; disallowTypeAnnotations?: boolean; @@ -44,7 +44,7 @@ interface ReportValueImport { inlineTypeSpecifiers: TSESTree.ImportSpecifier[]; } -type MessageIds = +export type MessageIds = | 'typeOverValue' | 'someImportsAreOnlyTypes' | 'avoidImportType' diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index 9904dc2e5336..54e8c132c53d 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -9,7 +9,7 @@ import { isValidFunctionExpressionReturnType, } from '../util/explicitReturnTypeUtils'; -type Options = [ +export type Options = [ { allowExpressions?: boolean; allowTypedFunctionExpressions?: boolean; @@ -21,7 +21,7 @@ type Options = [ allowIIFEs?: boolean; }, ]; -type MessageIds = 'missingReturnType'; +export type MessageIds = 'missingReturnType'; type FunctionNode = | TSESTree.ArrowFunctionExpression diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index 5947d292f8bd..2a885d275d37 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -12,12 +12,12 @@ import { getParameterPropertyHeadLoc, } from '../util/getMemberHeadLoc'; -type AccessibilityLevel = +export type AccessibilityLevel = | 'explicit' // require an accessor (including public) | 'no-public' // don't require public | 'off'; // don't check -interface Config { +export interface Config { accessibility?: AccessibilityLevel; ignoredMethodNames?: string[]; overrides?: { @@ -29,9 +29,9 @@ interface Config { }; } -type Options = [Config]; +export type Options = [Config]; -type MessageIds = +export type MessageIds = | 'addExplicitAccessibility' | 'missingAccessibility' | 'unwantedPublicAccessibility'; diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index c8981e403941..66e606d20538 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -16,7 +16,7 @@ import { isTypedFunctionExpression, } from '../util/explicitReturnTypeUtils'; -type Options = [ +export type Options = [ { allowArgumentsExplicitlyTypedAsAny?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; @@ -25,7 +25,7 @@ type Options = [ allowTypedFunctionExpressions?: boolean; }, ]; -type MessageIds = +export type MessageIds = | 'anyTypedArg' | 'anyTypedArgUnnamed' | 'missingArgType' diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts index 248ecd0d7e8a..11a5d8313a7e 100644 --- a/packages/eslint-plugin/src/rules/indent.ts +++ b/packages/eslint-plugin/src/rules/indent.ts @@ -17,8 +17,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('indent'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; const KNOWN_NODES = new Set([ // Class properties aren't yet supported by eslint... diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index dfafc8a8eff5..e90a726d7c49 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -131,6 +131,7 @@ import promiseFunctionAsync from './promise-function-async'; import quotes from './quotes'; import requireArraySortCompare from './require-array-sort-compare'; import requireAwait from './require-await'; +import requireTypesExports from './require-types-exports'; import restrictPlusOperands from './restrict-plus-operands'; import restrictTemplateExpressions from './restrict-template-expressions'; import returnAwait from './return-await'; @@ -281,6 +282,7 @@ export default { quotes: quotes, 'require-array-sort-compare': requireArraySortCompare, 'require-await': requireAwait, + 'require-types-exports': requireTypesExports, 'restrict-plus-operands': restrictPlusOperands, 'restrict-template-expressions': restrictTemplateExpressions, 'return-await': returnAwait, diff --git a/packages/eslint-plugin/src/rules/lines-between-class-members.ts b/packages/eslint-plugin/src/rules/lines-between-class-members.ts index 60da9308757a..2304f7ac405b 100644 --- a/packages/eslint-plugin/src/rules/lines-between-class-members.ts +++ b/packages/eslint-plugin/src/rules/lines-between-class-members.ts @@ -11,8 +11,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('lines-between-class-members'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; const schema = Object.values( deepMerge( diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index 428f31f3667e..13a398defdf7 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -4,10 +4,10 @@ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'; import { createRule, deepMerge } from '../util'; -type Delimiter = 'comma' | 'none' | 'semi'; +export type Delimiter = 'comma' | 'none' | 'semi'; // need type's implicit index sig for deepMerge // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -type TypeOptions = { +export type TypeOptions = { delimiter?: Delimiter; requireLast?: boolean; }; @@ -15,19 +15,19 @@ type TypeOptionsWithType = TypeOptions & { type: string; }; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -type BaseOptions = { +export type BaseOptions = { multiline?: TypeOptions; singleline?: TypeOptions; }; -type Config = BaseOptions & { +export type Config = BaseOptions & { overrides?: { typeLiteral?: BaseOptions; interface?: BaseOptions; }; multilineDetection?: 'brackets' | 'last-member'; }; -type Options = [Config]; -type MessageIds = +export type Options = [Config]; +export type MessageIds = | 'expectedComma' | 'expectedSemi' | 'unexpectedComma' diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 4713192770a7..c357095ae988 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -17,9 +17,9 @@ export type MessageIds = | 'incorrectOrder' | 'incorrectRequiredMembersOrder'; -type ReadonlyType = 'readonly-field' | 'readonly-signature'; +export type ReadonlyType = 'readonly-field' | 'readonly-signature'; -type MemberKind = +export type MemberKind = | ReadonlyType | 'accessor' | 'call-signature' @@ -31,7 +31,7 @@ type MemberKind = | 'signature' | 'static-initialization'; -type DecoratedMemberKind = +export type DecoratedMemberKind = | Exclude | 'accessor' | 'field' @@ -39,16 +39,16 @@ type DecoratedMemberKind = | 'method' | 'set'; -type NonCallableMemberKind = Exclude< +export type NonCallableMemberKind = Exclude< MemberKind, 'constructor' | 'readonly-signature' | 'signature' >; -type MemberScope = 'abstract' | 'instance' | 'static'; +export type MemberScope = 'abstract' | 'instance' | 'static'; -type Accessibility = TSESTree.Accessibility | '#private'; +export type Accessibility = TSESTree.Accessibility | '#private'; -type BaseMemberType = +export type BaseMemberType = | MemberKind | `${Accessibility}-${Exclude< MemberKind, @@ -59,26 +59,26 @@ type BaseMemberType = | `${MemberScope}-${NonCallableMemberKind}` | `decorated-${DecoratedMemberKind}`; -type MemberType = BaseMemberType | BaseMemberType[]; +export type MemberType = BaseMemberType | BaseMemberType[]; -type AlphabeticalOrder = +export type AlphabeticalOrder = | 'alphabetically-case-insensitive' | 'alphabetically' | 'natural-case-insensitive' | 'natural'; -type Order = AlphabeticalOrder | 'as-written'; +export type Order = AlphabeticalOrder | 'as-written'; -interface SortedOrderConfig { +export interface SortedOrderConfig { memberTypes?: MemberType[] | 'never'; optionalityOrder?: OptionalityOrder; order?: Order; } -type OrderConfig = MemberType[] | SortedOrderConfig | 'never'; -type Member = TSESTree.ClassElement | TSESTree.TypeElement; +export type OrderConfig = MemberType[] | SortedOrderConfig | 'never'; +export type Member = TSESTree.ClassElement | TSESTree.TypeElement; -type OptionalityOrder = 'optional-first' | 'required-first'; +export type OptionalityOrder = 'optional-first' | 'required-first'; export type Options = [ { diff --git a/packages/eslint-plugin/src/rules/no-array-delete.ts b/packages/eslint-plugin/src/rules/no-array-delete.ts index d9900a1df10f..96bd2fdc2568 100644 --- a/packages/eslint-plugin/src/rules/no-array-delete.ts +++ b/packages/eslint-plugin/src/rules/no-array-delete.ts @@ -8,7 +8,7 @@ import { getParserServices, } from '../util'; -type MessageId = 'noArrayDelete' | 'useSplice'; +export type MessageId = 'noArrayDelete' | 'useSplice'; export default createRule<[], MessageId>({ name: 'no-array-delete', diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 0369e66fe66f..0dee231a011f 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -10,12 +10,12 @@ enum Usefulness { Sometimes = 'may', } -type Options = [ +export type Options = [ { ignoredTypeNames?: string[]; }, ]; -type MessageIds = 'baseToString'; +export type MessageIds = 'baseToString'; export default createRule({ name: 'no-base-to-string', diff --git a/packages/eslint-plugin/src/rules/no-dupe-class-members.ts b/packages/eslint-plugin/src/rules/no-dupe-class-members.ts index 08dd0b35d3b8..e1ffecdd6dad 100644 --- a/packages/eslint-plugin/src/rules/no-dupe-class-members.ts +++ b/packages/eslint-plugin/src/rules/no-dupe-class-members.ts @@ -10,8 +10,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-dupe-class-members'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; export default createRule({ name: 'no-dupe-class-members', diff --git a/packages/eslint-plugin/src/rules/no-empty-function.ts b/packages/eslint-plugin/src/rules/no-empty-function.ts index 6a8e90ebaf13..79e6ffd9cc56 100644 --- a/packages/eslint-plugin/src/rules/no-empty-function.ts +++ b/packages/eslint-plugin/src/rules/no-empty-function.ts @@ -11,8 +11,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-empty-function'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; const schema = deepMerge( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- https://github.com/microsoft/TypeScript/issues/17002 diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts index 674b86d44bf9..0e56604d2567 100644 --- a/packages/eslint-plugin/src/rules/no-empty-interface.ts +++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts @@ -4,12 +4,12 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, isDefinitionFile } from '../util'; -type Options = [ +export type Options = [ { allowSingleExtends?: boolean; }, ]; -type MessageIds = 'noEmpty' | 'noEmptyWithSuper'; +export type MessageIds = 'noEmpty' | 'noEmptyWithSuper'; export default createRule({ name: 'no-empty-interface', diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index 0ed0f4c6b4de..bd1dde121c3f 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -13,8 +13,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-extra-parens'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; export default createRule({ name: 'no-extra-parens', diff --git a/packages/eslint-plugin/src/rules/no-extra-semi.ts b/packages/eslint-plugin/src/rules/no-extra-semi.ts index 4d68f2d6db46..d384cde41fc8 100644 --- a/packages/eslint-plugin/src/rules/no-extra-semi.ts +++ b/packages/eslint-plugin/src/rules/no-extra-semi.ts @@ -7,8 +7,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-extra-semi'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; export default createRule({ name: 'no-extra-semi', diff --git a/packages/eslint-plugin/src/rules/no-extraneous-class.ts b/packages/eslint-plugin/src/rules/no-extraneous-class.ts index a7c05f8cc5a8..6d975a454b9a 100644 --- a/packages/eslint-plugin/src/rules/no-extraneous-class.ts +++ b/packages/eslint-plugin/src/rules/no-extraneous-class.ts @@ -3,7 +3,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { allowConstructorOnly?: boolean; allowEmpty?: boolean; @@ -11,7 +11,7 @@ type Options = [ allowWithDecorator?: boolean; }, ]; -type MessageIds = 'empty' | 'onlyConstructor' | 'onlyStatic'; +export type MessageIds = 'empty' | 'onlyConstructor' | 'onlyStatic'; export default createRule({ name: 'no-extraneous-class', diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index b49516184317..f7b3f36ab506 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -15,7 +15,7 @@ import { typeMatchesSpecifier, } from '../util'; -type Options = [ +export type Options = [ { allowForKnownSafePromises?: TypeOrValueSpecifier[]; checkThenables?: boolean; @@ -24,7 +24,7 @@ type Options = [ }, ]; -type MessageId = +export type MessageId = | 'floating' | 'floatingVoid' | 'floatingUselessRejectionHandler' diff --git a/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts b/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts index 1658d471bc7b..585e78a177de 100644 --- a/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts +++ b/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts @@ -9,8 +9,8 @@ import { NullThrowsReasons, } from '../util'; -type Options = []; -type MessageIds = 'useTopLevelQualifier'; +export type Options = []; +export type MessageIds = 'useTopLevelQualifier'; export default createRule({ name: 'no-import-type-side-effects', diff --git a/packages/eslint-plugin/src/rules/no-inferrable-types.ts b/packages/eslint-plugin/src/rules/no-inferrable-types.ts index 51ead2ae476a..3b76ee94b0c6 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -4,13 +4,13 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, nullThrows, NullThrowsReasons } from '../util'; -type Options = [ +export type Options = [ { ignoreParameters?: boolean; ignoreProperties?: boolean; }, ]; -type MessageIds = 'noInferrableType'; +export type MessageIds = 'noInferrableType'; export default createRule({ name: 'no-inferrable-types', diff --git a/packages/eslint-plugin/src/rules/no-invalid-void-type.ts b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts index 8ae1604a210b..4d4189ac7dfc 100644 --- a/packages/eslint-plugin/src/rules/no-invalid-void-type.ts +++ b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts @@ -3,12 +3,12 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -interface Options { +export interface Options { allowInGenericTypeArguments?: [string, ...string[]] | boolean; allowAsThisParameter?: boolean; } -type MessageIds = +export type MessageIds = | 'invalidVoidForGeneric' | 'invalidVoidNotReturn' | 'invalidVoidNotReturnOrGeneric' diff --git a/packages/eslint-plugin/src/rules/no-loop-func.ts b/packages/eslint-plugin/src/rules/no-loop-func.ts index a34217453b0d..096e3618f5d4 100644 --- a/packages/eslint-plugin/src/rules/no-loop-func.ts +++ b/packages/eslint-plugin/src/rules/no-loop-func.ts @@ -10,8 +10,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-loop-func'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; export default createRule({ name: 'no-loop-func', diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts index 06938f9e4dcf..51d2bdee9f25 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -9,8 +9,10 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-loss-of-precision'); -type Options = InferOptionsTypeFromRule>; -type MessageIds = InferMessageIdsTypeFromRule>; +export type Options = InferOptionsTypeFromRule>; +export type MessageIds = InferMessageIdsTypeFromRule< + NonNullable +>; export default createRule({ name: 'no-loss-of-precision', diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts index 5a86056cb90d..d31cdd895e36 100644 --- a/packages/eslint-plugin/src/rules/no-magic-numbers.ts +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -11,8 +11,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-magic-numbers'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; // Extend base schema with additional property to ignore TS numeric literal types const schema = deepMerge( diff --git a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts index e8fdde8d1f33..d1d21612476c 100644 --- a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts +++ b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { checkNever: boolean; }, diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 1a58d884dc7c..2c386402a162 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -9,7 +9,7 @@ import { isRestParameterDeclaration, } from '../util'; -type Options = [ +export type Options = [ { checksConditionals?: boolean; checksVoidReturn?: ChecksVoidReturnOptions | boolean; @@ -17,7 +17,7 @@ type Options = [ }, ]; -interface ChecksVoidReturnOptions { +export interface ChecksVoidReturnOptions { arguments?: boolean; attributes?: boolean; properties?: boolean; @@ -25,7 +25,7 @@ interface ChecksVoidReturnOptions { variables?: boolean; } -type MessageId = +export type MessageId = | 'conditional' | 'spread' | 'voidReturnArgument' diff --git a/packages/eslint-plugin/src/rules/no-namespace.ts b/packages/eslint-plugin/src/rules/no-namespace.ts index c6b9213259be..67a4979358ba 100644 --- a/packages/eslint-plugin/src/rules/no-namespace.ts +++ b/packages/eslint-plugin/src/rules/no-namespace.ts @@ -3,13 +3,13 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, isDefinitionFile } from '../util'; -type Options = [ +export type Options = [ { allowDeclarations?: boolean; allowDefinitionFiles?: boolean; }, ]; -type MessageIds = 'moduleSyntaxIsPreferred'; +export type MessageIds = 'moduleSyntaxIsPreferred'; export default createRule({ name: 'no-namespace', diff --git a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts index 8545b0e1c110..f34637eeb42d 100644 --- a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts @@ -8,7 +8,7 @@ import { NullThrowsReasons, } from '../util'; -type MessageIds = 'noNonNull' | 'suggestOptionalChain'; +export type MessageIds = 'noNonNull' | 'suggestOptionalChain'; export default createRule<[], MessageIds>({ name: 'no-non-null-assertion', diff --git a/packages/eslint-plugin/src/rules/no-redeclare.ts b/packages/eslint-plugin/src/rules/no-redeclare.ts index b084d90650b0..782df71aadbc 100644 --- a/packages/eslint-plugin/src/rules/no-redeclare.ts +++ b/packages/eslint-plugin/src/rules/no-redeclare.ts @@ -4,8 +4,11 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, getNameLocationInGlobalDirectiveComment } from '../util'; -type MessageIds = 'redeclared' | 'redeclaredAsBuiltin' | 'redeclaredBySyntax'; -type Options = [ +export type MessageIds = + | 'redeclared' + | 'redeclaredAsBuiltin' + | 'redeclaredBySyntax'; +export type Options = [ { builtinGlobals?: boolean; ignoreDeclarationMerge?: boolean; diff --git a/packages/eslint-plugin/src/rules/no-require-imports.ts b/packages/eslint-plugin/src/rules/no-require-imports.ts index 1956694484f4..577b36371905 100644 --- a/packages/eslint-plugin/src/rules/no-require-imports.ts +++ b/packages/eslint-plugin/src/rules/no-require-imports.ts @@ -3,12 +3,12 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import * as util from '../util'; -type Options = [ +export type Options = [ { allow: string[]; }, ]; -type MessageIds = 'noRequireImports'; +export type MessageIds = 'noRequireImports'; export default util.createRule({ name: 'no-require-imports', diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index f66c21f6cdb1..78b59c53c775 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -8,8 +8,8 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type MessageIds = 'noShadow' | 'noShadowGlobal'; -type Options = [ +export type MessageIds = 'noShadow' | 'noShadowGlobal'; +export type Options = [ { allow?: string[]; builtinGlobals?: boolean; diff --git a/packages/eslint-plugin/src/rules/no-this-alias.ts b/packages/eslint-plugin/src/rules/no-this-alias.ts index 98006e56328d..5ddbc7463bf0 100644 --- a/packages/eslint-plugin/src/rules/no-this-alias.ts +++ b/packages/eslint-plugin/src/rules/no-this-alias.ts @@ -3,13 +3,13 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { allowDestructuring?: boolean; allowedNames?: string[]; }, ]; -type MessageIds = 'thisAssignment' | 'thisDestructure'; +export type MessageIds = 'thisAssignment' | 'thisDestructure'; export default createRule({ name: 'no-this-alias', diff --git a/packages/eslint-plugin/src/rules/no-throw-literal.ts b/packages/eslint-plugin/src/rules/no-throw-literal.ts index d893dbc164a5..2318c75b742e 100644 --- a/packages/eslint-plugin/src/rules/no-throw-literal.ts +++ b/packages/eslint-plugin/src/rules/no-throw-literal.ts @@ -10,9 +10,9 @@ import { isTypeUnknownType, } from '../util'; -type MessageIds = 'object' | 'undef'; +export type MessageIds = 'object' | 'undef'; -type Options = [ +export type Options = [ { allowThrowingAny?: boolean; allowThrowingUnknown?: boolean; diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index 53e64a3b5c69..cd7898f7e9c4 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -3,14 +3,14 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Values = +export type Values = | 'always' | 'in-intersections' | 'in-unions-and-intersections' | 'in-unions' | 'never'; -type Options = [ +export type Options = [ { allowAliases?: Values; allowCallbacks?: 'always' | 'never'; @@ -22,7 +22,7 @@ type Options = [ allowGenerics?: 'always' | 'never'; }, ]; -type MessageIds = 'noCompositionAlias' | 'noTypeAlias'; +export type MessageIds = 'noCompositionAlias' | 'noTypeAlias'; type CompositionType = | AST_NODE_TYPES.TSIntersectionType diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts index b472f75e5a0a..9900c20ed22e 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts @@ -5,14 +5,14 @@ import * as ts from 'typescript'; import { createRule, getParserServices, isStrongPrecedenceNode } from '../util'; -type MessageIds = +export type MessageIds = | 'comparingNullableToFalse' | 'comparingNullableToTrueDirect' | 'comparingNullableToTrueNegated' | 'direct' | 'negated'; -type Options = [ +export type Options = [ { allowComparingNullableBooleansToTrue?: boolean; allowComparingNullableBooleansToFalse?: boolean; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts index 44e10c5e33c8..aa35bda65f44 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -11,7 +11,7 @@ import { isUndefinedIdentifier, } from '../util'; -type MessageId = 'noUnnecessaryTemplateExpression'; +export type MessageId = 'noUnnecessaryTemplateExpression'; export default createRule<[], MessageId>({ name: 'no-unnecessary-template-expression', diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts index d482ef1b672a..711565a11d96 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts @@ -20,7 +20,7 @@ type ParameterCapableTSNode = | ts.TypeQueryNode | ts.TypeReferenceNode; -type MessageIds = 'unnecessaryTypeParameter'; +export type MessageIds = 'unnecessaryTypeParameter'; export default createRule<[], MessageIds>({ name: 'no-unnecessary-type-arguments', diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 69f5daaad619..b128178b6bc1 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -15,12 +15,12 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { typesToIgnore?: string[]; }, ]; -type MessageIds = 'contextuallyUnnecessary' | 'unnecessaryAssertion'; +export type MessageIds = 'contextuallyUnnecessary' | 'unnecessaryAssertion'; export default createRule({ name: 'no-unnecessary-type-assertion', diff --git a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts index 08950fa8d732..3b03af48065b 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts @@ -12,7 +12,7 @@ import { nullThrows, } from '../util'; -type MessageIds = +export type MessageIds = | 'unsafeArgument' | 'unsafeArraySpread' | 'unsafeSpread' diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index f496f02d9155..068dfe92a4b0 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -9,7 +9,7 @@ import { isTypeAnyType, } from '../util'; -type MessageIds = +export type MessageIds = | 'unsafeCall' | 'unsafeCallThis' | 'unsafeNew' diff --git a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts index 66488e37124a..a95633404d1d 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts @@ -3,8 +3,8 @@ import * as ts from 'typescript'; import * as util from '../util'; -type Options = []; -type MessageIds = 'unaryMinus'; +export type Options = []; +export type MessageIds = 'unaryMinus'; export default util.createRule({ name: 'no-unsafe-unary-minus', diff --git a/packages/eslint-plugin/src/rules/no-unused-expressions.ts b/packages/eslint-plugin/src/rules/no-unused-expressions.ts index 83c2ddd6c527..aab3bbf4f5b5 100644 --- a/packages/eslint-plugin/src/rules/no-unused-expressions.ts +++ b/packages/eslint-plugin/src/rules/no-unused-expressions.ts @@ -9,8 +9,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-unused-expressions'); -type MessageIds = InferMessageIdsTypeFromRule; -type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; export default createRule({ name: 'no-unused-expressions', diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index d577773de9ef..551bfe9ecc39 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -219,7 +219,7 @@ function isInInitializer( return false; } -interface Config { +export interface Config { functions?: boolean; classes?: boolean; enums?: boolean; @@ -228,8 +228,8 @@ interface Config { ignoreTypeReferences?: boolean; allowNamedExports?: boolean; } -type Options = [Config | 'nofunc']; -type MessageIds = 'noUseBeforeDefine'; +export type Options = [Config | 'nofunc']; +export type MessageIds = 'noUseBeforeDefine'; export default createRule({ name: 'no-use-before-define', diff --git a/packages/eslint-plugin/src/rules/no-useless-constructor.ts b/packages/eslint-plugin/src/rules/no-useless-constructor.ts index dcfc7dd976de..460132f0cb33 100644 --- a/packages/eslint-plugin/src/rules/no-useless-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-useless-constructor.ts @@ -10,8 +10,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-useless-constructor'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; /** * Check if method with accessibility is not useless diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 7b13cd8e2e9a..3dd793e79831 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -11,7 +11,7 @@ import { isUndefinedIdentifier, } from '../util'; -type MessageId = 'noUnnecessaryTemplateExpression'; +export type MessageId = 'noUnnecessaryTemplateExpression'; export default createRule<[], MessageId>({ name: 'no-useless-template-literals', diff --git a/packages/eslint-plugin/src/rules/no-var-requires.ts b/packages/eslint-plugin/src/rules/no-var-requires.ts index 9bb9fb7c1921..ec8e00078360 100644 --- a/packages/eslint-plugin/src/rules/no-var-requires.ts +++ b/packages/eslint-plugin/src/rules/no-var-requires.ts @@ -3,12 +3,12 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule, getStaticStringValue } from '../util'; -type Options = [ +export type Options = [ { allow: string[]; }, ]; -type MessageIds = 'noVarReqs'; +export type MessageIds = 'noVarReqs'; export default createRule({ name: 'no-var-requires', diff --git a/packages/eslint-plugin/src/rules/only-throw-error.ts b/packages/eslint-plugin/src/rules/only-throw-error.ts index 62ce268fc700..bc30a31b1428 100644 --- a/packages/eslint-plugin/src/rules/only-throw-error.ts +++ b/packages/eslint-plugin/src/rules/only-throw-error.ts @@ -10,9 +10,9 @@ import { isTypeUnknownType, } from '../util'; -type MessageIds = 'object' | 'undef'; +export type MessageIds = 'object' | 'undef'; -type Options = [ +export type Options = [ { allowThrowingAny?: boolean; allowThrowingUnknown?: boolean; diff --git a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts b/packages/eslint-plugin/src/rules/padding-line-between-statements.ts index ecd87a06a643..be4227903475 100644 --- a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts +++ b/packages/eslint-plugin/src/rules/padding-line-between-statements.ts @@ -35,14 +35,14 @@ interface NodeTestObject { test: NodeTest; } -interface PaddingOption { +export interface PaddingOption { blankLine: keyof typeof PaddingTypes; prev: string[] | string; next: string[] | string; } -type MessageIds = 'expectedBlankLine' | 'unexpectedBlankLine'; -type Options = PaddingOption[]; +export type MessageIds = 'expectedBlankLine' | 'unexpectedBlankLine'; +export type Options = PaddingOption[]; const LT = `[${Array.from( new Set(['\r\n', '\r', '\n', '\u2028', '\u2029']), diff --git a/packages/eslint-plugin/src/rules/parameter-properties.ts b/packages/eslint-plugin/src/rules/parameter-properties.ts index 5246594e912e..91408e162d37 100644 --- a/packages/eslint-plugin/src/rules/parameter-properties.ts +++ b/packages/eslint-plugin/src/rules/parameter-properties.ts @@ -3,7 +3,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, nullThrows } from '../util'; -type Modifier = +export type Modifier = | 'private readonly' | 'private' | 'protected readonly' @@ -12,16 +12,16 @@ type Modifier = | 'public' | 'readonly'; -type Prefer = 'class-property' | 'parameter-property'; +export type Prefer = 'class-property' | 'parameter-property'; -type Options = [ +export type Options = [ { allow?: Modifier[]; prefer?: Prefer; }, ]; -type MessageIds = 'preferClassProperty' | 'preferParameterProperty'; +export type MessageIds = 'preferClassProperty' | 'preferParameterProperty'; export default createRule({ name: 'parameter-properties', diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 60e53dbb61e6..a50f1f8fac46 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -13,13 +13,13 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('prefer-destructuring'); -type BaseOptions = InferOptionsTypeFromRule; -type EnforcementOptions = BaseOptions[1] & { +export type BaseOptions = InferOptionsTypeFromRule; +export type EnforcementOptions = BaseOptions[1] & { enforceForDeclarationWithTypeAnnotation?: boolean; }; -type Options = [BaseOptions[0], EnforcementOptions]; +export type Options = [BaseOptions[0], EnforcementOptions]; -type MessageIds = InferMessageIdsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; const destructuringTypeConfig: JSONSchema4 = { type: 'object', diff --git a/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts b/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts index 27572b4f8f7f..9ef9191cfa7d 100644 --- a/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts +++ b/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts @@ -2,7 +2,7 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type MessageIds = 'defineInitializer' | 'defineInitializerSuggestion'; +export type MessageIds = 'defineInitializer' | 'defineInitializerSuggestion'; export default createRule<[], MessageIds>({ name: 'prefer-enum-initializers', diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/compareNodes.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/compareNodes.ts index d9dce486ec91..7d6acc5671f8 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/compareNodes.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/compareNodes.ts @@ -119,7 +119,7 @@ function compareByVisiting( return NodeComparisonResult.Equal; } -type CompareNodesArgument = TSESTree.Node | null | undefined; +export type CompareNodesArgument = TSESTree.Node | null | undefined; function compareNodesUncached( nodeA: TSESTree.Node, nodeB: TSESTree.Node, diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 54c44f4edda9..dd7beac839a8 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -56,7 +56,7 @@ export interface ValidOperand { export interface InvalidOperand { type: OperandValidity.Invalid; } -type Operand = ValidOperand | InvalidOperand; +export type Operand = ValidOperand | InvalidOperand; const NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined; function isValidFalseBooleanCheckType( diff --git a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts index 6cb935c1db9a..9329d2c757fc 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts @@ -10,7 +10,7 @@ import { readonlynessOptionsSchema, } from '../util'; -type Options = [ +export type Options = [ { allow?: TypeOrValueSpecifier[]; checkParameterProperties?: boolean; @@ -18,7 +18,7 @@ type Options = [ treatMethodsAsReadonly?: boolean; }, ]; -type MessageIds = 'shouldBeReadonly'; +export type MessageIds = 'shouldBeReadonly'; export default createRule({ name: 'prefer-readonly-parameter-types', diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index f163b1aaab34..02adbc4640d0 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -14,8 +14,8 @@ import { getParameterPropertyHeadLoc, } from '../util/getMemberHeadLoc'; -type MessageIds = 'preferReadonly'; -type Options = [ +export type MessageIds = 'preferReadonly'; +export type Options = [ { onlyInlineLambdas?: boolean; }, diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index f6a22152043c..ac139d381e77 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -16,7 +16,7 @@ import { const EQ_OPERATORS = /^[=!]=/; const regexpp = new RegExpParser(); -type AllowedSingleElementEquality = 'always' | 'never'; +export type AllowedSingleElementEquality = 'always' | 'never'; export type Options = [ { @@ -24,7 +24,7 @@ export type Options = [ }, ]; -type MessageIds = 'preferEndsWith' | 'preferStartsWith'; +export type MessageIds = 'preferEndsWith' | 'preferStartsWith'; export default createRule({ name: 'prefer-string-starts-ends-with', diff --git a/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts b/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts index 6ae1f11720e1..4a6851686383 100644 --- a/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts +++ b/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts @@ -4,7 +4,7 @@ import type { RuleFix, RuleFixer } from '@typescript-eslint/utils/ts-eslint'; import { createRule } from '../util'; -type MessageIds = 'preferExpectErrorComment'; +export type MessageIds = 'preferExpectErrorComment'; export default createRule<[], MessageIds>({ name: 'prefer-ts-expect-error', diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index c191ce45b0db..3cb6a8c9b582 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -12,7 +12,7 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { allowAny?: boolean; allowedPromiseNames?: string[]; @@ -22,7 +22,7 @@ type Options = [ checkMethodDeclarations?: boolean; }, ]; -type MessageIds = 'missingAsync'; +export type MessageIds = 'missingAsync'; export default createRule({ name: 'promise-function-async', diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts new file mode 100644 index 000000000000..222bdeb1edc7 --- /dev/null +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -0,0 +1,504 @@ +import { + ImplicitLibVariable, + ScopeType, +} from '@typescript-eslint/scope-manager'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule, findVariable } from '../util'; + +export type MessageIds = 'requireTypeExport'; + +export default createRule<[], MessageIds>({ + name: 'require-types-exports', + meta: { + type: 'suggestion', + docs: { + description: 'Require exporting types that are used in exported entities', + recommended: 'strict', + }, + messages: { + requireTypeExport: + '"{{ name }}" is used in other exports from this file, so it should also be exported.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const externalizedTypes = new Set(); + const reportedTypes = new Set(); + + function collectImportedTypes( + node: + | TSESTree.ImportSpecifier + | TSESTree.ImportNamespaceSpecifier + | TSESTree.ImportDefaultSpecifier, + ): void { + externalizedTypes.add(node.local.name); + } + + function collectExportedTypes(node: TSESTree.Program): void { + const isCollectableType = ( + node: TSESTree.Node, + ): node is + | TSESTree.TSTypeAliasDeclaration + | TSESTree.TSInterfaceDeclaration + | TSESTree.TSEnumDeclaration + | TSESTree.TSModuleDeclaration => { + return [ + AST_NODE_TYPES.TSTypeAliasDeclaration, + AST_NODE_TYPES.TSInterfaceDeclaration, + AST_NODE_TYPES.TSEnumDeclaration, + AST_NODE_TYPES.TSModuleDeclaration, + ].includes(node.type); + }; + + node.body.forEach(statement => { + if ( + statement.type === AST_NODE_TYPES.ExportNamedDeclaration && + statement.declaration && + isCollectableType(statement.declaration) && + statement.declaration.id.type === AST_NODE_TYPES.Identifier + ) { + externalizedTypes.add(statement.declaration.id.name); + } + }); + } + + function visitExportedFunctionDeclaration( + node: ( + | TSESTree.ExportNamedDeclaration + | TSESTree.DefaultExportDeclarations + | TSESTree.ArrowFunctionExpression + ) & { + declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; + }, + ): void { + checkNodeTypes(node.declaration); + } + + function visitExportedVariableDeclaration( + node: TSESTree.ExportNamedDeclaration & { + declaration: TSESTree.VariableDeclaration; + }, + ): void { + for (const declaration of node.declaration.declarations) { + checkNodeTypes(declaration); + } + } + + function visitExportedTypeDeclaration( + node: TSESTree.ExportNamedDeclaration & { + declaration: + | TSESTree.TSTypeAliasDeclaration + | TSESTree.TSInterfaceDeclaration; + }, + ): void { + checkNodeTypes(node.declaration); + } + + function visitExportDefaultDeclaration( + node: TSESTree.ExportDefaultDeclaration, + ): void { + checkNodeTypes(node.declaration); + } + + function checkNodeTypes(node: TSESTree.Node): void { + const { typeReferences, typeQueries } = getVisibleTypesRecursively( + node, + context.sourceCode, + ); + + typeReferences.forEach(checkTypeReference); + typeQueries.forEach(checkTypeQuery); + } + + function checkTypeReference(node: TSESTree.TSTypeReference): void { + const name = getTypeName(node.typeName); + + const isExternalized = externalizedTypes.has(name); + const isReported = reportedTypes.has(name); + + if (isExternalized || isReported) { + return; + } + + context.report({ + node, + messageId: 'requireTypeExport', + data: { + name, + }, + }); + + reportedTypes.add(name); + } + + function checkTypeQuery(node: TSESTree.TSTypeQuery): void { + const name = context.sourceCode.getText(node); + const isReported = reportedTypes.has(name); + + if (isReported) { + return; + } + context.report({ + node, + messageId: 'requireTypeExport', + data: { + name, + }, + }); + + reportedTypes.add(name); + } + + return { + 'ImportDeclaration ImportSpecifier': collectImportedTypes, + 'ImportDeclaration ImportNamespaceSpecifier': collectImportedTypes, + 'ImportDeclaration ImportDefaultSpecifier': collectImportedTypes, + + Program: collectExportedTypes, + + 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': + visitExportedFunctionDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSDeclareFunction"]': + visitExportedFunctionDeclaration, + + 'ExportDefaultDeclaration[declaration.type="FunctionDeclaration"]': + visitExportedFunctionDeclaration, + + 'ExportDefaultDeclaration[declaration.type="ArrowFunctionExpression"]': + visitExportedFunctionDeclaration, + + 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': + visitExportedVariableDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"]': + visitExportedTypeDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedTypeDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedTypeDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedTypeDeclaration, + + ExportDefaultDeclaration: visitExportDefaultDeclaration, + }; + }, +}); + +function getTypeName(typeName: TSESTree.EntityName): string { + switch (typeName.type) { + case AST_NODE_TYPES.Identifier: + return typeName.name; + + case AST_NODE_TYPES.TSQualifiedName: + // Namespaced types are not exported directly, so we check the + // leftmost part of the name. + return getTypeName(typeName.left); + + case AST_NODE_TYPES.ThisExpression: + return 'this'; + } +} + +function getVisibleTypesRecursively( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +): { + typeReferences: Set; + typeQueries: Set; +} { + const typeReferences = new Set(); + const typeQueries = new Set(); + const visited = new Set(); + + collect(node); + + function collect(node: TSESTree.Node | null | undefined): void { + if (!node || visited.has(node)) { + return; + } + + visited.add(node); + + switch (node.type) { + case AST_NODE_TYPES.VariableDeclarator: + collect(node.id); + collect(node.init); + break; + + case AST_NODE_TYPES.Identifier: { + collect(node.typeAnnotation?.typeAnnotation); + + // Resolve the variable to its declaration (in cases where the variable is referenced) + const scope = sourceCode.getScope(node); + const variableNode = findVariable(scope, node.name); + + variableNode?.defs.forEach(def => { + collect(def.name); + collect(def.node); + }); + break; + } + + case AST_NODE_TYPES.ObjectExpression: + node.properties.forEach(property => { + const nodeToCheck = + property.type === AST_NODE_TYPES.Property + ? property.value + : property.argument; + + collect(nodeToCheck); + }); + break; + + case AST_NODE_TYPES.ArrayExpression: + node.elements.forEach(element => { + const nodeToCheck = + element?.type === AST_NODE_TYPES.SpreadElement + ? element.argument + : element; + + collect(nodeToCheck); + }); + break; + + case AST_NODE_TYPES.NewExpression: + case AST_NODE_TYPES.CallExpression: + collect(node.callee); + node.arguments.forEach(arg => collect(arg)); + node.typeArguments?.params.forEach(param => collect(param)); + break; + + case AST_NODE_TYPES.BinaryExpression: + case AST_NODE_TYPES.LogicalExpression: + collect(node.left); + collect(node.right); + break; + + case AST_NODE_TYPES.ConditionalExpression: + collect(node.consequent); + collect(node.alternate); + break; + + case AST_NODE_TYPES.ArrowFunctionExpression: + case AST_NODE_TYPES.FunctionDeclaration: + case AST_NODE_TYPES.FunctionExpression: + case AST_NODE_TYPES.TSDeclareFunction: + node.typeParameters?.params.forEach(param => collect(param.constraint)); + node.params.forEach(collect); + collect(node.returnType?.typeAnnotation); + + if (node.body) { + collectFunctionReturnStatements(node).forEach(collect); + } + break; + + case AST_NODE_TYPES.AssignmentPattern: + collect(node.left); + break; + + case AST_NODE_TYPES.RestElement: + collect(node.argument); + collect(node.typeAnnotation?.typeAnnotation); + break; + + case AST_NODE_TYPES.ObjectPattern: + node.properties.forEach(collect); + collect(node.typeAnnotation?.typeAnnotation); + break; + + case AST_NODE_TYPES.ArrayPattern: + node.elements.forEach(collect); + collect(node.typeAnnotation?.typeAnnotation); + + break; + + case AST_NODE_TYPES.ReturnStatement: + collect(node.argument); + break; + + case AST_NODE_TYPES.TSTypeReference: { + const scope = sourceCode.getScope(node); + const variable = findVariable(scope, getTypeName(node.typeName)); + + const isBuiltinType = variable instanceof ImplicitLibVariable; + + const isGenericTypeArg = + (variable?.scope.type === ScopeType.function || + variable?.scope.type === ScopeType.type) && + variable.identifiers.every( + id => id.parent.type === AST_NODE_TYPES.TSTypeParameter, + ); + + if (!isBuiltinType && !isGenericTypeArg) { + typeReferences.add(node); + } + + node.typeArguments?.params.forEach(collect); + break; + } + + case AST_NODE_TYPES.TSTypeOperator: + collect(node.typeAnnotation); + break; + + case AST_NODE_TYPES.TSTypeQuery: + if (isInsideFunctionDeclaration(node)) { + typeQueries.add(node); + } + break; + + case AST_NODE_TYPES.TSArrayType: + collect(node.elementType); + break; + + case AST_NODE_TYPES.TSTupleType: + node.elementTypes.forEach(collect); + break; + + case AST_NODE_TYPES.TSUnionType: + case AST_NODE_TYPES.TSIntersectionType: + node.types.forEach(collect); + break; + + case AST_NODE_TYPES.TSTypeLiteral: + node.members.forEach(collect); + break; + + case AST_NODE_TYPES.TSTemplateLiteralType: + node.types.forEach(collect); + break; + + case AST_NODE_TYPES.TSTypeAliasDeclaration: + collect(node.typeAnnotation); + break; + + case AST_NODE_TYPES.TSInterfaceDeclaration: + node.body.body.forEach(collect); + break; + + case AST_NODE_TYPES.TSPropertySignature: + collect(node.typeAnnotation?.typeAnnotation); + break; + + case AST_NODE_TYPES.TSQualifiedName: + collect(node.parent); + break; + + case AST_NODE_TYPES.TSAsExpression: + collect(node.expression); + collect(node.typeAnnotation); + break; + + case AST_NODE_TYPES.TSIndexedAccessType: + collect(node.objectType); + collect(node.indexType); + break; + + default: + break; + } + } + + return { + typeReferences, + typeQueries, + }; +} + +function isInsideFunctionDeclaration(node: TSESTree.Node): boolean { + const functionNodes = new Set([ + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.TSDeclareFunction, + ]); + + if (!node.parent) { + return false; + } + + if (functionNodes.has(node.parent.type)) { + return true; + } + + return isInsideFunctionDeclaration(node.parent); +} + +function collectFunctionReturnStatements( + functionNode: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression, +): Set { + const isArrowFunctionReturn = + functionNode.type === AST_NODE_TYPES.ArrowFunctionExpression && + functionNode.body.type !== AST_NODE_TYPES.BlockStatement; + + if (isArrowFunctionReturn) { + return new Set([functionNode.body]); + } + + const returnStatements = new Set(); + + forEachReturnStatement(functionNode, returnNode => + returnStatements.add(returnNode), + ); + + return returnStatements; +} + +// Heavily inspired by: +// https://github.com/typescript-eslint/typescript-eslint/blob/103de6eed/packages/eslint-plugin/src/util/astUtils.ts#L47-L80 +function forEachReturnStatement( + functionNode: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression, + visitor: (returnNode: TSESTree.ReturnStatement) => void, +): void { + return traverse(functionNode.body); + + function traverse(node: TSESTree.Node | null): void { + switch (node?.type) { + case AST_NODE_TYPES.ReturnStatement: + return visitor(node); + + case AST_NODE_TYPES.SwitchStatement: + return node.cases.forEach(traverse); + + case AST_NODE_TYPES.SwitchCase: + return node.consequent.forEach(traverse); + + case AST_NODE_TYPES.BlockStatement: + return node.body.forEach(traverse); + + case AST_NODE_TYPES.DoWhileStatement: + case AST_NODE_TYPES.ForInStatement: + case AST_NODE_TYPES.ForOfStatement: + case AST_NODE_TYPES.WhileStatement: + case AST_NODE_TYPES.ForStatement: + case AST_NODE_TYPES.WithStatement: + case AST_NODE_TYPES.CatchClause: + case AST_NODE_TYPES.LabeledStatement: + return traverse(node.body); + + case AST_NODE_TYPES.IfStatement: + traverse(node.consequent); + traverse(node.alternate); + return; + + case AST_NODE_TYPES.TryStatement: + traverse(node.block); + traverse(node.handler); + traverse(node.finalizer); + return; + } + } +} diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 1953c15c70cb..f70603498d8f 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -11,7 +11,7 @@ import { isTypeFlagSet, } from '../util'; -type Options = [ +export type Options = [ { allowAny?: boolean; allowBoolean?: boolean; @@ -22,7 +22,7 @@ type Options = [ }, ]; -type MessageIds = 'bigintAndNumber' | 'invalid' | 'mismatched'; +export type MessageIds = 'bigintAndNumber' | 'invalid' | 'mismatched'; export default createRule({ name: 'restrict-plus-operands', diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index cc719fe7fb7b..3447ddab7b3c 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -49,11 +49,11 @@ const optionTesters = ( option: `allow${type}` as const, tester, })); -type Options = [ +export type Options = [ { [Type in (typeof optionTesters)[number]['option']]?: boolean }, ]; -type MessageId = 'invalidType'; +export type MessageId = 'invalidType'; export default createRule({ name: 'restrict-template-expressions', diff --git a/packages/eslint-plugin/src/rules/space-before-function-paren.ts b/packages/eslint-plugin/src/rules/space-before-function-paren.ts index 78ea98239db7..3d8c813ccc46 100644 --- a/packages/eslint-plugin/src/rules/space-before-function-paren.ts +++ b/packages/eslint-plugin/src/rules/space-before-function-paren.ts @@ -4,8 +4,8 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, isOpeningParenToken } from '../util'; -type Option = 'always' | 'never'; -type FuncOption = Option | 'ignore'; +export type Option = 'always' | 'never'; +export type FuncOption = Option | 'ignore'; export type Options = [ | Option diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 1452d7de6cdb..e9a784c6374d 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -20,7 +20,7 @@ interface SwitchMetadata { readonly containsNonLiteralType: boolean; } -type Options = [ +export type Options = [ { /** * If `true`, allow `default` cases on switch statements with exhaustive @@ -39,7 +39,7 @@ type Options = [ }, ]; -type MessageIds = +export type MessageIds = | 'switchIsNotExhaustive' | 'dangerousDefaultCase' | 'addMissingCases'; diff --git a/packages/eslint-plugin/src/rules/triple-slash-reference.ts b/packages/eslint-plugin/src/rules/triple-slash-reference.ts index a45662c33d4c..7bec0ae018bf 100644 --- a/packages/eslint-plugin/src/rules/triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/triple-slash-reference.ts @@ -3,14 +3,14 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { lib?: 'always' | 'never'; path?: 'always' | 'never'; types?: 'always' | 'never' | 'prefer-import'; }, ]; -type MessageIds = 'tripleSlashReference'; +export type MessageIds = 'tripleSlashReference'; export default createRule({ name: 'triple-slash-reference', diff --git a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts index 47fbf80d8ac0..0f0988a3332b 100644 --- a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts +++ b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts @@ -31,8 +31,8 @@ interface Config extends WhitespaceRule { type WhitespaceRules = Required; -type Options = [Config?]; -type MessageIds = +export type Options = [Config?]; +export type MessageIds = | 'expectedSpaceAfter' | 'expectedSpaceBefore' | 'unexpectedSpaceAfter' diff --git a/packages/eslint-plugin/src/rules/typedef.ts b/packages/eslint-plugin/src/rules/typedef.ts index 187f4d620365..690386f2c9ce 100644 --- a/packages/eslint-plugin/src/rules/typedef.ts +++ b/packages/eslint-plugin/src/rules/typedef.ts @@ -14,9 +14,9 @@ const enum OptionKeys { VariableDeclarationIgnoreFunction = 'variableDeclarationIgnoreFunction', } -type Options = { [k in OptionKeys]?: boolean }; +export type Options = { [k in OptionKeys]?: boolean }; -type MessageIds = 'expectedTypedef' | 'expectedTypedefNamed'; +export type MessageIds = 'expectedTypedef' | 'expectedTypedefNamed'; export default createRule<[Options], MessageIds>({ name: 'typedef', diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index 9b416ea4a570..6a87b9ea6f78 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -14,7 +14,7 @@ import { // Rule Definition //------------------------------------------------------------------------------ -interface Config { +export interface Config { ignoreStatic: boolean; } diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts index c8fa2f85f7df..14db3f84df78 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -51,12 +51,12 @@ type MethodDefinition = | TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition; -type MessageIds = +export type MessageIds = | 'omittingRestParameter' | 'omittingSingleParameter' | 'singleParameterDifference'; -type Options = [ +export type Options = [ { ignoreDifferentlyNamedParameters?: boolean; }, diff --git a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts index b899b23c391d..74d09f305701 100644 --- a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts +++ b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts @@ -16,7 +16,7 @@ import { nullThrows, } from '../util'; -type MessageIds = +export type MessageIds = | 'useUnknown' | 'useUnknownSpreadArgs' | 'useUnknownArrayDestructuringPattern' diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index be28069d2877..761d7e8d0f82 100644 --- a/packages/eslint-plugin/src/util/getESLintCoreRule.ts +++ b/packages/eslint-plugin/src/util/getESLintCoreRule.ts @@ -1,7 +1,7 @@ import { ESLintUtils } from '@typescript-eslint/utils'; import { builtinRules } from 'eslint/use-at-your-own-risk'; -interface RuleMap { +export interface RuleMap { /* eslint-disable @typescript-eslint/consistent-type-imports -- more concise to use inline imports */ 'arrow-parens': typeof import('eslint/lib/rules/arrow-parens'); 'block-spacing': typeof import('eslint/lib/rules/block-spacing'); @@ -42,7 +42,7 @@ interface RuleMap { /* eslint-enable @typescript-eslint/consistent-type-imports */ } -type RuleId = keyof RuleMap; +export type RuleId = keyof RuleMap; export const getESLintCoreRule = (ruleId: R): RuleMap[R] => ESLintUtils.nullThrows( diff --git a/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts index a8f6bc40afbd..f1aa6c9ce68c 100644 --- a/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts +++ b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts @@ -5,7 +5,7 @@ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'; import { isArrowToken, isOpeningParenToken } from './astUtils'; -type FunctionNode = +export type FunctionNode = | TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; diff --git a/packages/eslint-plugin/src/util/getOperatorPrecedence.ts b/packages/eslint-plugin/src/util/getOperatorPrecedence.ts index 8aac3d6fbd2d..d14c05e963a6 100644 --- a/packages/eslint-plugin/src/util/getOperatorPrecedence.ts +++ b/packages/eslint-plugin/src/util/getOperatorPrecedence.ts @@ -295,7 +295,7 @@ export function getOperatorPrecedenceForNode( } } -type TSESTreeOperatorKind = +export type TSESTreeOperatorKind = | ValueOf | ValueOf; diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index d5d07b6ba7e1..4c95b7fd4db4 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -5,7 +5,7 @@ import { ESLintUtils, } from '@typescript-eslint/utils'; -interface WrappingFixerParams { +export interface WrappingFixerParams { /** Source code. */ sourceCode: Readonly; /** The node we want to modify. */ diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-types-exports.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-types-exports.shot new file mode 100644 index 000000000000..f515f012d362 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-types-exports.shot @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 1`] = ` +"Incorrect + +type Arg = string; +type Result = number; + +export function strLength(arg: Arg): Result { + ~~~ Expected type "Arg" to be exported + ~~~~~~ Expected type "Result" to be exported + return arg.length; +} + +interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; + ~~~~~ Expected type "Fruit" to be exported + +enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; + ~~~~~ Expected type "Color" to be exported +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 2`] = ` +"Correct + +export type Arg = string; +export type Result = number; + +export function strLength(arg: Arg): Result { + return arg.length; +} + +export interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; + +export enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +" +`; diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json b/packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json new file mode 100644 index 000000000000..6168cfcb8d54 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["esnext", "DOM"] + } +} diff --git a/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts b/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts index 7e1207a222d2..90229443cf7c 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts @@ -77,7 +77,7 @@ const IGNORED_FILTER = { regex: /.gnored/.source, }; -type Cases = { code: string[]; options: Omit }[]; +export type Cases = { code: string[]; options: Omit }[]; export function createTestCases(cases: Cases): void { const createValidTestCases = (): TSESLint.ValidTestCase[] => diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/base-cases.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/base-cases.ts index 28b75a91697d..d61a5556ccca 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/base-cases.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/base-cases.ts @@ -5,8 +5,8 @@ import type { PreferOptionalChainOptions, } from '../../../src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions'; -type MutateFn = (c: string) => string; -type BaseCaseCreator = (args: { +export type MutateFn = (c: string) => string; +export type BaseCaseCreator = (args: { operator: '&&' | '||'; mutateCode?: MutateFn; mutateOutput?: MutateFn; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts new file mode 100644 index 000000000000..5629431c0711 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -0,0 +1,3493 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/require-types-exports'; +import { getFixturesRootDir } from '../RuleTester'; + +const rootPath = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: rootPath, + project: './tsconfig-with-dom.json', + }, +}); + +ruleTester.run('require-types-exports', rule, { + valid: [ + 'export function f(): void {}', + 'export const f = (): void => {};', + + 'export function f(a: number): void {}', + 'export const f = (a: number): void => {};', + + 'export function f(a: any): void {}', + 'export const f = (a: any): void => {};', + + 'export function f(a: null): void {}', + 'export const f = (a: null): void => {};', + + 'export function f(a: string | number): void {}', + 'export const f = (a: string | number): void => {};', + + 'export function f(a?: string | number): void {}', + 'export const f = (a?: string | number): void => {};', + + 'export function f(a: number): string {}', + 'export const f = (a: number): string => {};', + + 'export function f(...args: any[]): void {}', + 'export const f = (...args: any[]): void => {};', + + 'export function f(...args: unknown[]): void {}', + 'export const f = (...args: unknown[]): void => {};', + + 'export default function f(): void {}', + 'export default (): void => {};', + + ` + type A = number; + function f(a: A): A { + return a; + } + `, + + ` + type A = number; + const f = (a: A): A => a; + `, + + ` + type A = number; + type B = string; + function f(a: A | B): any { + return a; + } + `, + + ` + type A = number; + type B = string; + const f = (a: A | B): any => a; + `, + + ` + type A = number; + declare function f(a: A): void; + `, + + ` + type A = number; + function f({ a }: { a: A }): A {} + `, + + ` + type A = number; + const f = ({ a }: { a: A }): A => {}; + `, + + ` + type A = number; + type B = string; + function f([a, b]: [A, B]): void {} + `, + + ` + type A = number; + type B = string; + const f = ([a, b]: [A, B]): void => {}; + `, + + ` + type A = number; + function f(a: A): void {} + `, + + ` + type A = number; + const f = (a: A): void => {}; + `, + + ` + interface A { + a: number; + } + + function f(a: A): A { + return a; + } + `, + + ` + interface A { + a: number; + } + + const f = (a: A): A => a; + `, + + ` + export type A = number; + export function f(a: A): void {} + `, + + ` + export type A = number; + export const f = (a: A): void => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(a: A | B): void {} + `, + + ` + export type A = number; + export type B = string; + export const f = (a: A | B): void => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(a: A & B): void {} + `, + + ` + export type A = number; + export type B = string; + export const f = (a: A & B): void => {}; + `, + + ` + export type A = number; + export function f(...args: A[]): void {} + `, + + ` + export type A = number; + export const f = (...args: A[]): void => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(args: { a: A; b: B; c: number }): void {} + `, + + ` + export type A = number; + export type B = string; + export const f = (args: { a: A; b: B; c: number }): void => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(args: [A, B]): void {} + `, + + ` + export type A = number; + export type B = string; + export const f = (args: [A, B]): void => {}; + `, + + ` + export type A = number; + export function f(a: A = 1): void {} + `, + + ` + export type A = number; + export const f = (a: A = 1): void => {}; + `, + + ` + export type A = number; + export function f(): A {} + `, + + ` + export type A = number; + export const f = (): A => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(): A | B {} + `, + + ` + export type A = number; + export type B = string; + export const f = (): A | B => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(): A & B {} + `, + + ` + export type A = number; + export type B = string; + export const f = (): A & B => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(): [A, B] {} + `, + + ` + export type A = number; + export type B = string; + export const f = (): [A, B] => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(): { a: A; b: B } {} + `, + + ` + export type A = number; + export type B = string; + export const f = (): { a: A; b: B } => {}; + `, + + ` + import { testFunction, type Arg } from './module'; + + export function f(a: Arg): void {} + `, + + ` + import { Arg } from './types'; + + export function f(a: Arg): void {} + `, + + ` + import type { Arg } from './types'; + + export function f(a: Arg): void {} + `, + + ` + import type { ImportedArg as Arg } from './types'; + + export function f(a: Arg): void {} + `, + + ` + import type { Arg } from './types'; + + export function f(a: T): void {} + `, + + ` + export type R = number; + + export function f() { + const value: { num: R } = { + num: 1, + }; + + return value; + } + `, + + ` + import type { A } from './types'; + + export type T1 = number; + + export interface T2 { + key: number; + } + + export const value: { a: { b: { c: T1 } } } | [string, T2 | A] = { + a: { + b: { + c: 1, + }, + }, + }; + `, + + ` + import type { A } from './types'; + + export type T1 = number; + + export interface T2 { + key: number; + } + + const value: { a: { b: { c: T1 } } } | [string, T2 | A] = { + a: { + b: { + c: 1, + }, + }, + }; + + export default value; + `, + + ` + export enum Fruit { + Apple, + Banana, + Cherry, + } + + export function f(a: Fruit): void {} + `, + + ` + export function f(arg: Record>) { + return arg; + } + `, + + ` + export function f>>(arg: T) { + return arg; + } + `, + + ` + export function f string>>(arg: T) { + return arg; + } + `, + + ` + export class Wrapper { + work(other: this) {} + } + `, + + ` + export namespace A { + export namespace B { + export type C = number; + } + } + + export function a(arg: A.B.C) { + return arg; + } + `, + + ` + import * as ts from 'typescript'; + + export function a(arg: ts.Type) { + return arg; + } + `, + + ` + import ts from 'typescript'; + + export function a(arg: ts.Type) { + return arg; + } + `, + + ` + declare const element: HTMLElement; + + export default element; + `, + + ` + export const date: Date = new Date(); + `, + + ` + import ts from 'typescript'; + + export enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const apple: Fruit.Apple; + + export type A = number; + export type B = string; + export type C = boolean; + + export interface D { + key: string; + } + + function func>( + arg: T, + ): T | ts.Type { + return arg; + } + + export const value = { + apple, + func, + }; + `, + + ` + export function func1() { + return func2(1); + } + + export type A = number; + + export function func2(arg: A) { + return 1; + } + `, + + 'export type ValueOf = T[keyof T];', + + ` + const fruits = { apple: 'apple' }; + export type Fruits = typeof fruits; + + export function getFruit(key: Key): Fruits[Key] { + return fruits[key]; + } + `, + + ` + const fruits = { apple: 'apple' }; + + export function doWork(): number { + const fruit: keyof typeof fruits = 'apple'; + + return 1; + } + `, + ], + + invalid: [ + { + code: ` + type Arg = number; + + export function f(a: Arg): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (a: Arg): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export default function (a: Arg): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 37, + endColumn: 40, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export default (a: Arg): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 28, + endColumn: 31, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export function f(a: Arg, b: Arg): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (a: Arg, b: Arg): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1, b: Arg2): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 43, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1, b: Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 43, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + + interface Arg2 { + a: string; + } + + export function f(a: Arg1, b: Arg2): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 8, + column: 39, + endColumn: 43, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + + interface Arg2 { + a: string; + } + + export const f = (a: Arg1, b: Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 8, + column: 39, + endColumn: 43, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1 | Arg2): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1 | Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1 & Arg2): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1 & Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f([a, b]: [Arg1, Arg2, number]): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 36, + endColumn: 40, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 42, + endColumn: 46, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + type Arg3 = boolean; + + export function f([a, b]: [Arg1, Arg2, number], c: Arg3): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 36, + endColumn: 40, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 42, + endColumn: 46, + data: { + name: 'Arg2', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 60, + endColumn: 64, + data: { + name: 'Arg3', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = ([a, b]: [Arg1, Arg2, number]): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 36, + endColumn: 40, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 42, + endColumn: 46, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f({ a, b }: { a: Arg1; b: Arg2; c: number }): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 42, + endColumn: 46, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 51, + endColumn: 55, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = ({ a, b }: { a: Arg1; b: Arg2; c: number }): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 42, + endColumn: 46, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 51, + endColumn: 55, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export function f(...args: Arg[]): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 36, + endColumn: 39, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (...args: Arg[]): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 36, + endColumn: 39, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export function f(a: Arg = 1): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (a: Arg = 1): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + export function f(a: Fruit): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 30, + endColumn: 35, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + export const f = (a: Fruit): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 30, + endColumn: 35, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 37, + endColumn: 40, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: T): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 38, + endColumn: 41, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 42, + endColumn: 45, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Ret = string; + + export function f(): Ret {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Ret', + }, + }, + ], + }, + + { + code: ` + type Ret = string; + + export const f = (): Ret => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Ret', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): Ret1 | Ret2 {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): Ret1 | Ret2 => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): Ret1 & Ret2 {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): Ret1 & Ret2 => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): [Ret1, Ret2, number, Ret1] {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 35, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): [Ret1, Ret2, number, Ret1] => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 35, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): { a: Ret1; b: Ret2; c: number; d: Ret1 } {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 35, + endColumn: 39, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): { a: Ret1; b: Ret2; c: number; d: Ret1 } => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 35, + endColumn: 39, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret = string; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 37, + endColumn: 40, + data: { + name: 'Ret', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret = string; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 38, + endColumn: 41, + data: { + name: 'Ret', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + const a = (a: Arg): void => {}; + + export default a; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 23, + endColumn: 26, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + const a = function (a: Arg): void {}; + + export default a; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 32, + endColumn: 35, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export declare function f(a: Arg): void; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 38, + endColumn: 41, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export declare function f(a: Arg): Arg; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 38, + endColumn: 41, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type R = number; + + export function f() { + const value: { num: R } = { + num: 1, + }; + + return value; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 32, + data: { + name: 'R', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = boolean; + type Ret = string; + + export declare function f( + a: { b: { c: Arg1 | number | { d: T } } }, + e: Arg1, + ): { a: { b: T | Ret } }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 45, + endColumn: 49, + data: { + name: 'Arg2', + }, + }, + { + messageId: 'requireTypeExport', + line: 7, + column: 24, + endColumn: 28, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 9, + column: 26, + endColumn: 29, + data: { + name: 'Ret', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export declare function f(a: Arg1): true; + export declare function f(a: Arg2): false; + export declare function f(a: Arg1 | Arg2): boolean; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 38, + endColumn: 42, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 38, + endColumn: 42, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f1 = (a: Arg1): void => {}, + f2 = (a: Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 35, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 20, + endColumn: 24, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + namespace A { + export namespace B { + export type C = number; + } + } + + export function a(arg: A.B.C) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 32, + endColumn: 37, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + namespace A { + export type B = number; + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + namespace A { + export interface B { + value: number; + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 10, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + namespace A { + export enum B { + Value1, + Value2, + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 11, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + namespace A { + export namespace B { + export type C = number; + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 10, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + import type { A } from './types'; + + type T1 = number; + + interface T2 { + key: number; + } + + export const value: { a: { b: { c: T1 } } } | [string, T2 | A] = { + a: { + b: { + c: 1, + }, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 10, + column: 44, + endColumn: 46, + data: { + name: 'T1', + }, + }, + { + messageId: 'requireTypeExport', + line: 10, + column: 64, + endColumn: 66, + data: { + name: 'T2', + }, + }, + ], + }, + + { + code: ` + import type { A } from './types'; + + type T1 = number; + + interface T2 { + key: number; + } + + const value: { a: { b: { c: T1 } } } | [string, T2 | A] = { + a: { + b: { + c: 1, + }, + }, + }; + + export default value; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 10, + column: 37, + endColumn: 39, + data: { + name: 'T1', + }, + }, + { + messageId: 'requireTypeExport', + line: 10, + column: 57, + endColumn: 59, + data: { + name: 'T2', + }, + }, + ], + }, + + { + code: ` + type T1 = number; + + interface T2 { + key: number; + } + + type T3 = boolean; + + export const value: + | { + a: T1; + b: { + c: T2; + }; + } + | T3[] = { + a: 1, + b: { + c: { + key: 1, + }, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 12, + column: 18, + endColumn: 20, + data: { + name: 'T1', + }, + }, + { + messageId: 'requireTypeExport', + line: 14, + column: 20, + endColumn: 22, + data: { + name: 'T2', + }, + }, + { + messageId: 'requireTypeExport', + line: 17, + column: 13, + endColumn: 15, + data: { + name: 'T3', + }, + }, + ], + }, + + { + code: ` + type A = string; + type B = string; + + const apple: A = 'apple'; + const banana: B = 'banana'; + + export const value = { + path: { + to: { + apple, + and: { + banana, + }, + }, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 22, + endColumn: 23, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = string; + type B = string; + + const apple: A = 'apple'; + const banana: B = 'banana'; + + const value = { + path: { + to: { + apple, + and: { + banana, + }, + }, + }, + }; + + export default value; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 22, + endColumn: 23, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = string; + type B = string; + + const apple: A = 'apple'; + const banana: B = 'banana'; + + const value = { + spreadObject: { ...{ apple } }, + spreadArray: [...[banana]], + }; + + export default value; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 22, + endColumn: 23, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana], + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 22, + endColumn: 27, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana] as const, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 22, + endColumn: 27, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana] as any, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 22, + endColumn: 27, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple = 'apple'; + const banana = 'banana'; + + export const value = { + path: { + to: [apple, banana] as [Fruit, Fruit], + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 9, + column: 37, + endColumn: 42, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple = 'apple'; + const banana = 'banana'; + + export const value = { + path: { + to: [apple, banana] as Fruit | number, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 9, + column: 36, + endColumn: 41, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + type C = boolean; + type D = symbol; + + declare const a: [A, B] | ([Array, Set] & Exclude); + + export const value = { a }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 27, + endColumn: 28, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 7, + column: 30, + endColumn: 31, + data: { + name: 'B', + }, + }, + { + messageId: 'requireTypeExport', + line: 7, + column: 43, + endColumn: 44, + data: { + name: 'C', + }, + }, + { + messageId: 'requireTypeExport', + line: 7, + column: 51, + endColumn: 52, + data: { + name: 'D', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + export const value = { + func: (arg: A): B => 'apple', + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 27, + endColumn: 28, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + export const value = { + func: function (arg: A): B { + return 'apple'; + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 32, + endColumn: 33, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 36, + endColumn: 37, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + const func = (arg: A): B => 'apple'; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 28, + endColumn: 29, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + const func = function (arg: A): B { + return 'apple'; + }; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 38, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const func = (arg: T): T => 'apple'; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 33, + endColumn: 34, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const func = function (arg: T): T { + return 'apple'; + }; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 42, + endColumn: 43, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + export const value = { + func: (arg: T): T => 'apple', + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 28, + endColumn: 29, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + export const value = { + func: function (arg: T): T { + return 'apple'; + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 38, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + declare function func(arg: T): T; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare function func(arg: T): T; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 41, + endColumn: 46, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + a, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 26, + endColumn: 37, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + key: () => a, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 26, + endColumn: 37, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + key: function () { + return a; + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 26, + endColumn: 37, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Item = { + key: string; + value: number; + }; + + type ItemKey = Item['key']; + + const item: Item = { key: 'apple', value: 1 }; + + const map = new Map([['apple', item]]); + + export const value = { + map, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 9, + column: 21, + endColumn: 25, + data: { + name: 'Item', + }, + }, + { + messageId: 'requireTypeExport', + line: 11, + column: 29, + endColumn: 36, + data: { + name: 'ItemKey', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: (() => item)(), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 21, + endColumn: 22, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => a)(item), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 21, + endColumn: 22, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: T) => a)(item), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 28, + endColumn: 29, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => [a])(item), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 21, + endColumn: 22, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => ({ a }))(item), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 21, + endColumn: 22, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + export function func1(arg: R): R { + return func2(arg); + } + + declare function func2(arg: T): T; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + export function func1(arg: R): R { + doWork(String(arg)); + + return arg; + } + + declare function doWork(arg: B): void; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = number; + + export function func1(arg: R) { + return func2(arg); + } + + declare function func2(arg: B): B; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 9, + column: 37, + endColumn: 38, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = number; + type C = number; + + export function func1(arg: R) { + if (Math.random() > 0.5) { + return func2(arg); + } else { + return func3(arg); + } + } + + declare function func2(arg: B): B; + declare function func3(arg: C): C; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 14, + column: 37, + endColumn: 38, + data: { + name: 'B', + }, + }, + { + messageId: 'requireTypeExport', + line: 15, + column: 37, + endColumn: 38, + data: { + name: 'C', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = number; + + export function func1(arg: R) { + const a = (() => { + return func2(arg); + })(); + + return arg; + } + + declare function func2(arg: B): B; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = number; + + export function func1(arg: R) { + return arg as B; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 25, + endColumn: 26, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + export function func1(arg: R): R { + function doWork(arg2: B): void {} + + doWork(String(arg)); + + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type ItemsMap = Record; + type Key = keyof ItemsMap; + + export function get(key: K): ItemsMap[K] { + return key as never; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 42, + data: { + name: 'Key', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 53, + endColumn: 61, + data: { + name: 'ItemsMap', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const value: A = 1; + + export function func() { + return Math.random() > 0.5 && value; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 22, + endColumn: 23, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + const valueA: A = 1; + const valueB: B = 'test'; + + export function func() { + return Math.random() > 0.5 ? valueA : valueB; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 23, + endColumn: 24, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + declare function func(): string; + + type A = string; + + export default func(); + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 29, + endColumn: 30, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type Apple = 'apple'; + type Banana = 'banana'; + + export type Fruites = Apple | Banana; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 36, + data: { + name: 'Apple', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 45, + data: { + name: 'Banana', + }, + }, + ], + }, + + { + code: ` + type A = number; + + export interface B { + a: A; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 14, + endColumn: 15, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + interface B { + b: string; + } + + export namespace C { + export type D = A; + export type E = B; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 9, + column: 27, + endColumn: 28, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 10, + column: 27, + endColumn: 28, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = 'test'; + export type B = \`test-\${A}\`; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 3, + column: 33, + endColumn: 34, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + const fruits = { apple: 'apple' }; + + export function getFruit( + key: Key, + ): (typeof fruits)[Key] { + return fruits[key]; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 52, + endColumn: 65, + data: { + name: 'typeof fruits', + }, + }, + ], + }, + + { + code: ` + const fruits = { apple: 'apple' }; + + export declare function processFruit( + fruit: F, + ): void; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 56, + endColumn: 75, + data: { + name: 'typeof fruits.apple', + }, + }, + ], + }, + + { + code: ` + const fruits = { apple: 'apple' }; + + export declare function processFruit< + F extends Record, + >(fruit: F): void; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 34, + endColumn: 47, + data: { + name: 'typeof fruits', + }, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/require-types-exports.shot b/packages/eslint-plugin/tests/schema-snapshots/require-types-exports.shot new file mode 100644 index 000000000000..2f0fbf6d8dfc --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/require-types-exports.shot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes require-types-exports 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index f0d48be1d34d..9a3334d387fe 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -156,6 +156,7 @@ export default ( '@typescript-eslint/require-array-sort-compare': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': 'error', '@typescript-eslint/restrict-template-expressions': 'error', 'no-return-await': 'off', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index 7aeb158346c3..85b43fa789eb 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -82,6 +82,7 @@ export default ( '@typescript-eslint/prefer-return-this-type': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': [ 'error', { diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index 71615896c4dd..f208b0a4a286 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -49,6 +49,7 @@ export default ( '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unified-signatures': 'error', },