diff --git a/packages/ast-spec/src/declaration/ExportAndImportKind.ts b/packages/ast-spec/src/declaration/ExportAndImportKind.ts index e8d90b767981..fe25bf739124 100644 --- a/packages/ast-spec/src/declaration/ExportAndImportKind.ts +++ b/packages/ast-spec/src/declaration/ExportAndImportKind.ts @@ -1,4 +1,4 @@ -type ExportAndImportKind = 'type' | 'value'; +export type ExportAndImportKind = 'type' | 'value'; export type ExportKind = ExportAndImportKind; export type ImportKind = ExportAndImportKind; diff --git a/packages/ast-spec/tests/util/parsers/parser-types.ts b/packages/ast-spec/tests/util/parsers/parser-types.ts index 6c96e3d893f6..add78f34e5c4 100644 --- a/packages/ast-spec/tests/util/parsers/parser-types.ts +++ b/packages/ast-spec/tests/util/parsers/parser-types.ts @@ -1,6 +1,6 @@ -type SnapshotPathFn = (i: number) => string; +export type SnapshotPathFn = (i: number) => string; -interface SuccessSnapshotPaths { +export interface SuccessSnapshotPaths { readonly ast: SnapshotPathFn; readonly tokens: SnapshotPathFn; } diff --git a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts index 26fcc60d8d2b..1b632fdd0972 100644 --- a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts +++ b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts @@ -87,14 +87,14 @@ function escapeTemplateString(code: string): string { return fixed; } -type Options = [ +export type Options = [ { // This option exists so that rules like type-annotation-spacing can exist without every test needing a prettier-ignore formatWithPrettier?: boolean; }, ]; -type MessageIds = +export type MessageIds = | 'invalidFormatting' | 'invalidFormattingErrorTest' | 'noUnnecessaryNoFormat' 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..a96076bf4ca8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/require-types-exports.mdx @@ -0,0 +1,87 @@ +--- +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 file, it is often useful to export also all the types that are used in their declarations. +Doing so ensures consumers of the file can directly import and use those types when using those entities. + +Otherwise, consumers may have 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 the entities. + +## Examples + + + + +```ts +interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; +``` + +```ts +const fruits = { + apple: '🍏', + banana: '🍌', +}; + +export const getFruit = (key: keyof typeof fruits) => fruits[key]; +``` + +```ts +enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +``` + + + + +```ts +export interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; +``` + +```ts +export const fruits = { + apple: '🍏', + banana: '🍌', +}; + +export const getFruit = (key: keyof typeof fruits) => fruits[key]; +``` + +```ts +export enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +``` + + + + +## When Not To Use It + +If your files utilize many complex self-referential types that you don't want external consumers to reference, you may want to avoid this rule for those cases. +You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule. diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index cd77c7dc2835..1026da596f9f 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -147,6 +147,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/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index 061df20cdd65..95efeca0cd5b 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -8,7 +8,7 @@ import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint'; export = { - parserOptions: { project: false, program: null, projectService: false }, + parserOptions: { program: null, project: false, projectService: false }, rules: { '@typescript-eslint/await-thenable': 'off', '@typescript-eslint/consistent-return': 'off', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts index c235c02e7b81..e5be4357f195 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -61,10 +61,10 @@ export = { { allowAny: false, allowBoolean: false, + allowNever: false, allowNullish: false, allowNumber: false, allowRegExp: false, - allowNever: false, }, ], '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 b00f5eb0fc14..4090df8ffe99 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -79,6 +79,7 @@ export = { '@typescript-eslint/related-getter-setter-pairs': '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 0e655d1464ca..318ae49b83e5 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -43,6 +43,7 @@ export = { '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', '@typescript-eslint/prefer-namespace-keyword': '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 08330497cb82..5b6badddfc3d 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -73,13 +73,13 @@ function typeNeedsParentheses(node: TSESTree.Node): boolean { } export type OptionString = 'array' | 'array-simple' | 'generic'; -type Options = [ +export type Options = [ { default: OptionString; readonly?: OptionString; }, ]; -type MessageIds = +export type MessageIds = | 'errorStringArray' | 'errorStringArrayReadonly' | 'errorStringArraySimple' diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index 6fb49227470a..06550d6d49ab 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -15,7 +15,7 @@ import { } from '../util'; import { getForStatementHeadLoc } from '../util/getForStatementHeadLoc'; -type MessageId = +export type MessageId = | 'await' | 'awaitUsingOfNonAsyncDisposable' | 'convertToOrdinaryFor' diff --git a/packages/eslint-plugin/src/rules/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index 61ded7aabf4b..ffa347713ad3 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -4,12 +4,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 { minimumDescriptionLength?: number; 'ts-check'?: DirectiveConfig; 'ts-expect-error'?: DirectiveConfig; @@ -19,7 +19,7 @@ interface Options { const defaultMinimumDescriptionLength = 3; -type MessageIds = +export type MessageIds = | 'replaceTsIgnoreWithTsExpectError' | 'tsDirectiveComment' | '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 1819abab966b..2e5cbfb5293f 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -11,8 +11,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 9283de326301..e616ff577ca9 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -9,7 +9,7 @@ import { getStaticMemberAccessValue, } from '../util'; -type Options = [ +export type Options = [ { enforceForClassFields?: boolean; exceptMethods?: string[]; @@ -17,7 +17,7 @@ type Options = [ ignoreOverrideMethods?: boolean; }, ]; -type MessageIds = 'missingThis'; +export type MessageIds = 'missingThis'; export default createRule({ name: 'class-methods-use-this', diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index 9c92cd98188c..3fb841c122df 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -4,8 +4,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 15ab691b38dd..cc9c82a11187 100644 --- a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts +++ b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts @@ -10,11 +10,11 @@ import { nullThrows, } from '../util'; -type MessageIds = +export type MessageIds = | 'preferIndexSignature' | 'preferIndexSignatureSuggestion' | 'preferRecord'; -type Options = ['index-signature' | 'record']; +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 2c18a3cb979f..427ede7fe6f7 100644 --- a/packages/eslint-plugin/src/rules/consistent-return.ts +++ b/packages/eslint-plugin/src/rules/consistent-return.ts @@ -13,10 +13,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.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 8713405115d9..cd7d514e0f0c 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -21,7 +21,7 @@ export type MessageIds = | 'replaceObjectTypeAssertionWithAnnotation' | 'replaceObjectTypeAssertionWithSatisfies' | 'unexpectedObjectTypeAssertion'; -type OptUnion = +export type OptUnion = | { assertionStyle: 'angle-bracket' | 'as'; objectLiteralTypeAssertions?: 'allow' | 'allow-as-parameter' | 'never'; diff --git a/packages/eslint-plugin/src/rules/consistent-type-exports.ts b/packages/eslint-plugin/src/rules/consistent-type-exports.ts index eb610ae84105..214d4402ced4 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-exports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-exports.ts @@ -14,7 +14,7 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { fixMixedExportsWithInlineTypeSpecifier: boolean; }, @@ -34,7 +34,7 @@ interface ReportValueExport { valueSpecifiers: 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 40b088d29245..6910005c47d7 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -16,10 +16,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 = [ { disallowTypeAnnotations?: boolean; fixStyle?: FixStyle; @@ -45,7 +45,7 @@ interface ReportValueImport { valueSpecifiers: TSESTree.ImportClause[]; } -type MessageIds = +export type MessageIds = | 'avoidImportType' | 'noImportTypeAnnotations' | 'someImportsAreOnlyTypes' 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 186241cb72a2..a8ee4aa3766b 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -11,7 +11,7 @@ import { isValidFunctionExpressionReturnType, } from '../util/explicitReturnTypeUtils'; -type Options = [ +export type Options = [ { allowConciseArrowFunctionExpressionsStartingWithVoid?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; @@ -23,7 +23,7 @@ type Options = [ allowTypedFunctionExpressions?: 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 7dff44c330f9..07b871077fdf 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -14,12 +14,12 @@ import { } from '../util/getMemberHeadLoc'; import { rangeToLoc } from '../util/rangeToLoc'; -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?: { @@ -31,9 +31,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 496719db3f37..eaa7b8baf0b4 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -18,7 +18,7 @@ import { isTypedFunctionExpression, } from '../util/explicitReturnTypeUtils'; -type Options = [ +export type Options = [ { allowArgumentsExplicitlyTypedAsAny?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; @@ -27,7 +27,7 @@ type Options = [ allowTypedFunctionExpressions?: boolean; }, ]; -type MessageIds = +export type MessageIds = | 'anyTypedArg' | 'anyTypedArgUnnamed' | 'missingArgType' diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index de51a8bea55d..548710ee0dfe 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -118,6 +118,7 @@ import promiseFunctionAsync from './promise-function-async'; import relatedGetterSetterPairs from './related-getter-setter-pairs'; 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'; @@ -250,6 +251,7 @@ const rules = { 'related-getter-setter-pairs': relatedGetterSetterPairs, '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/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 83b297152267..dfffdb16d1dc 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -18,9 +18,9 @@ export type MessageIds = | 'incorrectOrder' | 'incorrectRequiredMembersOrder'; -type ReadonlyType = 'readonly-field' | 'readonly-signature'; +export type ReadonlyType = 'readonly-field' | 'readonly-signature'; -type MemberKind = +export type MemberKind = | 'accessor' | 'call-signature' | 'constructor' @@ -32,7 +32,7 @@ type MemberKind = | 'static-initialization' | ReadonlyType; -type DecoratedMemberKind = +export type DecoratedMemberKind = | 'accessor' | 'field' | 'get' @@ -40,16 +40,16 @@ type DecoratedMemberKind = | 'set' | Exclude; -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 = '#private' | TSESTree.Accessibility; +export type Accessibility = '#private' | TSESTree.Accessibility; -type BaseMemberType = +export type BaseMemberType = | `${Accessibility}-${Exclude< MemberKind, 'readonly-signature' | 'signature' | 'static-initialization' @@ -60,26 +60,26 @@ type BaseMemberType = | `decorated-${DecoratedMemberKind}` | MemberKind; -type MemberType = BaseMemberType | BaseMemberType[]; +export type MemberType = BaseMemberType | BaseMemberType[]; -type AlphabeticalOrder = +export type AlphabeticalOrder = | 'alphabetically' | 'alphabetically-case-insensitive' | 'natural' | 'natural-case-insensitive'; -type Order = 'as-written' | AlphabeticalOrder; +export type Order = 'as-written' | AlphabeticalOrder; -interface SortedOrderConfig { +export interface SortedOrderConfig { memberTypes?: 'never' | MemberType[]; optionalityOrder?: OptionalityOrder; order?: Order; } -type OrderConfig = 'never' | MemberType[] | SortedOrderConfig; +export type OrderConfig = 'never' | MemberType[] | SortedOrderConfig; 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/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index 736bfdadc407..03500d825163 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -21,7 +21,7 @@ import { } from '../util'; import { Modifiers, parseOptions, SCHEMA } from './naming-convention-utils'; -type MessageIds = +export type MessageIds = | 'doesNotMatchFormat' | 'doesNotMatchFormatTrimmed' | 'missingAffix' @@ -32,7 +32,7 @@ type MessageIds = // Note that this intentionally does not strictly type the modifiers/types properties. // This is because doing so creates a huge headache, as the rule's code doesn't need to care. // The JSON Schema strictly types these properties, so we know the user won't input invalid config. -type Options = Selector[]; +export type Options = Selector[]; // This essentially mirrors ESLint's `camelcase` rule // note that that rule ignores leading and trailing underscores and only checks those in the middle of a variable name @@ -789,5 +789,3 @@ function requiresQuoting( : `${node.value}`; return _requiresQuoting(name, target); } - -export type { MessageIds, Options }; diff --git a/packages/eslint-plugin/src/rules/no-array-delete.ts b/packages/eslint-plugin/src/rules/no-array-delete.ts index a179418a7b01..900ac576db08 100644 --- a/packages/eslint-plugin/src/rules/no-array-delete.ts +++ b/packages/eslint-plugin/src/rules/no-array-delete.ts @@ -9,7 +9,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 b681f820a3b0..e56a312b0fba 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -18,12 +18,12 @@ enum Usefulness { Sometimes = 'may', } -type Options = [ +export type Options = [ { ignoredTypeNames?: string[]; }, ]; -type MessageIds = 'baseArrayJoin' | 'baseToString'; +export type MessageIds = 'baseArrayJoin' | 'baseToString'; export default createRule({ name: 'no-base-to-string', diff --git a/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts index 101f3514835b..d6ab0531ad80 100644 --- a/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts @@ -8,7 +8,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type MessageId = +export type MessageId = | 'confusingAssign' | 'confusingEqual' | 'confusingOperator' 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 fe4a50c861d6..4bdfd016eb3c 100644 --- a/packages/eslint-plugin/src/rules/no-dupe-class-members.ts +++ b/packages/eslint-plugin/src/rules/no-dupe-class-members.ts @@ -12,8 +12,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 ac51f32692a2..f805ca0e44d3 100644 --- a/packages/eslint-plugin/src/rules/no-empty-function.ts +++ b/packages/eslint-plugin/src/rules/no-empty-function.ts @@ -13,8 +13,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 defaultOptions: Options = [ { diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts index 855d228dbd34..1a2948bc7fb3 100644 --- a/packages/eslint-plugin/src/rules/no-empty-interface.ts +++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts @@ -5,12 +5,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-extraneous-class.ts b/packages/eslint-plugin/src/rules/no-extraneous-class.ts index 6a80ba9b876d..b5a4a19a1cb1 100644 --- a/packages/eslint-plugin/src/rules/no-extraneous-class.ts +++ b/packages/eslint-plugin/src/rules/no-extraneous-class.ts @@ -4,7 +4,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { allowConstructorOnly?: boolean; allowEmpty?: boolean; @@ -12,7 +12,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 dbbad264d7a0..880ae86cb838 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -18,7 +18,7 @@ import { typeMatchesSomeSpecifier, } from '../util'; -type Options = [ +export type Options = [ { allowForKnownSafeCalls?: TypeOrValueSpecifier[]; allowForKnownSafePromises?: TypeOrValueSpecifier[]; @@ -28,7 +28,7 @@ type Options = [ }, ]; -type MessageId = +export type MessageId = | 'floating' | 'floatingFixAwait' | 'floatingFixVoid' 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 12887dd2331e..a63fe2c85a16 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 @@ -10,8 +10,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 8c8c7c5b1dee..8c18f786e143 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -5,13 +5,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 ce41636f9ff3..ea2981794d06 100644 --- a/packages/eslint-plugin/src/rules/no-invalid-void-type.ts +++ b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts @@ -4,12 +4,12 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -interface Options { +export interface Options { allowAsThisParameter?: boolean; allowInGenericTypeArguments?: boolean | [string, ...string[]]; } -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 0a5fde3dc173..7446d767a6d8 100644 --- a/packages/eslint-plugin/src/rules/no-loop-func.ts +++ b/packages/eslint-plugin/src/rules/no-loop-func.ts @@ -7,13 +7,13 @@ import type { InferOptionsTypeFromRule, } from '../util'; -import { createRule } from '../util'; +import { createRule, isNodeInside } from '../util'; 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', @@ -153,8 +153,7 @@ export default createRule({ if ( kind === 'let' && declaration && - declaration.range[0] > loopNode.range[0] && - declaration.range[1] < loopNode.range[1] + isNodeInside(declaration, loopNode) ) { return true; } 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 1fad4ba75799..9c3374280d96 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -8,8 +8,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 9bfbab7a7b48..6ecddefd0645 100644 --- a/packages/eslint-plugin/src/rules/no-magic-numbers.ts +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -13,8 +13,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 3529b02998de..9419bde84ed0 100644 --- a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts +++ b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts @@ -6,7 +6,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 d806f4c59bfa..82b7c3d0ce30 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -15,7 +15,7 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { checksConditionals?: boolean; checksSpreads?: boolean; @@ -23,7 +23,7 @@ type Options = [ }, ]; -interface ChecksVoidReturnOptions { +export interface ChecksVoidReturnOptions { arguments?: boolean; attributes?: boolean; inheritedMethods?: boolean; @@ -32,7 +32,7 @@ interface ChecksVoidReturnOptions { variables?: boolean; } -type MessageId = +export type MessageId = | 'conditional' | 'predicate' | 'spread' diff --git a/packages/eslint-plugin/src/rules/no-namespace.ts b/packages/eslint-plugin/src/rules/no-namespace.ts index 38567e5c90d1..02421a25dea9 100644 --- a/packages/eslint-plugin/src/rules/no-namespace.ts +++ b/packages/eslint-plugin/src/rules/no-namespace.ts @@ -4,13 +4,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 d31e9a03da63..fd66c1cef9b0 100644 --- a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts @@ -9,7 +9,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 99051a7b463f..841f36323e03 100644 --- a/packages/eslint-plugin/src/rules/no-redeclare.ts +++ b/packages/eslint-plugin/src/rules/no-redeclare.ts @@ -5,8 +5,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 26dcbcd6c821..3836e06f82a4 100644 --- a/packages/eslint-plugin/src/rules/no-require-imports.ts +++ b/packages/eslint-plugin/src/rules/no-require-imports.ts @@ -4,13 +4,13 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import * as util from '../util'; -type Options = [ +export type Options = [ { allow?: string[]; allowAsImport?: boolean; }, ]; -type MessageIds = 'noRequireImports'; +export type MessageIds = 'noRequireImports'; export default util.createRule({ name: 'no-require-imports', diff --git a/packages/eslint-plugin/src/rules/no-restricted-types.ts b/packages/eslint-plugin/src/rules/no-restricted-types.ts index b4d9e427c07f..51ffd0032eb4 100644 --- a/packages/eslint-plugin/src/rules/no-restricted-types.ts +++ b/packages/eslint-plugin/src/rules/no-restricted-types.ts @@ -4,7 +4,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, objectReduceKey } from '../util'; -type Types = Record< +export type Types = Record< string, | boolean | string diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index 6a3605e79b8e..7827954cbc45 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -6,8 +6,8 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule } from '../util'; import { isTypeImport } from '../util/isTypeImport'; -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 842b6bb6edf9..3db6a87ef932 100644 --- a/packages/eslint-plugin/src/rules/no-this-alias.ts +++ b/packages/eslint-plugin/src/rules/no-this-alias.ts @@ -4,13 +4,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-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index f06d5aa94202..f63b74c69964 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -4,14 +4,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' | 'in-unions-and-intersections' | 'never'; -type Options = [ +export type Options = [ { allowAliases?: Values; allowCallbacks?: 'always' | 'never'; @@ -23,7 +23,7 @@ type Options = [ allowTupleTypes?: Values; }, ]; -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 069055d67add..cfc34d47b23e 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 @@ -11,14 +11,14 @@ import { isStrongPrecedenceNode, } from '../util'; -type MessageIds = +export type MessageIds = | 'comparingNullableToFalse' | 'comparingNullableToTrueDirect' | 'comparingNullableToTrueNegated' | 'direct' | 'negated'; -type Options = [ +export type Options = [ { allowComparingNullableBooleansToFalse?: boolean; allowComparingNullableBooleansToTrue?: 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 90ad42fb0169..898ab2c38220 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -15,7 +15,7 @@ import { } from '../util'; import { rangeToLoc } from '../util/rangeToLoc'; -type MessageId = 'noUnnecessaryTemplateExpression'; +export type MessageId = 'noUnnecessaryTemplateExpression'; const evenNumOfBackslashesRegExp = /(?({ 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 c73d8717c3e1..ba8b8be80b60 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -19,12 +19,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 60a3e01a9d51..4ae7f4bcc28b 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts @@ -14,7 +14,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 2c29caa2c9e1..a1f665518eaa 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -11,7 +11,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 fcfca3145db6..1dd4becd1875 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 8c28b5cfb8d8..8496ceee707d 100644 --- a/packages/eslint-plugin/src/rules/no-unused-expressions.ts +++ b/packages/eslint-plugin/src/rules/no-unused-expressions.ts @@ -10,8 +10,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; const defaultOptions: Options = [ { 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 712ccff66369..fa5252a7ea71 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -3,7 +3,7 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { DefinitionType } from '@typescript-eslint/scope-manager'; import { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { createRule, isNodeInside } from '../util'; import { referenceContainsTypeQuery } from '../util/referenceContainsTypeQuery'; const SENTINEL_TYPE = @@ -144,10 +144,7 @@ function isClassRefInClassDecorator( } for (const deco of variable.defs[0].node.decorators) { - if ( - reference.identifier.range[0] >= deco.range[0] && - reference.identifier.range[1] <= deco.range[1] - ) { + if (isNodeInside(reference.identifier, deco)) { return true; } } @@ -203,7 +200,7 @@ function isInInitializer( return false; } -interface Config { +export interface Config { allowNamedExports?: boolean; classes?: boolean; enums?: boolean; @@ -212,8 +209,8 @@ interface Config { typedefs?: boolean; variables?: boolean; } -type Options = ['nofunc' | Config]; -type MessageIds = 'noUseBeforeDefine'; +export type Options = ['nofunc' | Config]; +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 a91525ab742c..785cdbf8547c 100644 --- a/packages/eslint-plugin/src/rules/no-useless-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-useless-constructor.ts @@ -12,8 +12,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-var-requires.ts b/packages/eslint-plugin/src/rules/no-var-requires.ts index 19fcd3795294..d677f35c3329 100644 --- a/packages/eslint-plugin/src/rules/no-var-requires.ts +++ b/packages/eslint-plugin/src/rules/no-var-requires.ts @@ -4,12 +4,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 87f6a1e34152..ada9779653a3 100644 --- a/packages/eslint-plugin/src/rules/only-throw-error.ts +++ b/packages/eslint-plugin/src/rules/only-throw-error.ts @@ -15,9 +15,9 @@ import { typeOrValueSpecifiersSchema, } from '../util'; -type MessageIds = 'object' | 'undef'; +export type MessageIds = 'object' | 'undef'; -type Options = [ +export type Options = [ { allow?: TypeOrValueSpecifier[]; allowThrowingAny?: boolean; diff --git a/packages/eslint-plugin/src/rules/parameter-properties.ts b/packages/eslint-plugin/src/rules/parameter-properties.ts index 227f30abc014..cfd4323be0b6 100644 --- a/packages/eslint-plugin/src/rules/parameter-properties.ts +++ b/packages/eslint-plugin/src/rules/parameter-properties.ts @@ -4,7 +4,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, nullThrows } from '../util'; -type Modifier = +export type Modifier = | 'private' | 'private readonly' | 'protected' @@ -13,16 +13,16 @@ type Modifier = | 'public readonly' | '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 9ad46867a946..ee4f61767dbe 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -15,13 +15,13 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('prefer-destructuring'); -type BaseOptions = InferOptionsTypeFromRule; -type EnforcementOptions = { +export type BaseOptions = InferOptionsTypeFromRule; +export type EnforcementOptions = { enforceForDeclarationWithTypeAnnotation?: boolean; } & BaseOptions[1]; -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 00dae8cccb96..a339a3232045 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 8cbc5469f0d7..932b2af5523e 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 @@ -120,7 +120,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 c0f460259707..a0a42ed94419 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 @@ -58,7 +58,7 @@ export interface ValidOperand { export interface InvalidOperand { type: OperandValidity.Invalid; } -type Operand = InvalidOperand | ValidOperand; +export type Operand = InvalidOperand | ValidOperand; 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 0c90c4a4f85a..71e5efe2f74d 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts @@ -12,7 +12,7 @@ import { readonlynessOptionsSchema, } from '../util'; -type Options = [ +export type Options = [ { allow?: TypeOrValueSpecifier[]; checkParameterProperties?: boolean; @@ -20,7 +20,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 15256502ec20..a393888926cf 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -15,8 +15,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 259272d3dd37..53a9eb38c6ce 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 @@ -18,7 +18,7 @@ import { const EQ_OPERATORS = /^[=!]=/; const regexpp = new RegExpParser(); -type AllowedSingleElementEquality = 'always' | 'never'; +export type AllowedSingleElementEquality = 'always' | 'never'; export type Options = [ { @@ -26,7 +26,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 dc01b4a59a08..13541d4174ca 100644 --- a/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts +++ b/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts @@ -5,7 +5,7 @@ import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; 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 d1ec3ee7f649..06fdd024c0e9 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -13,7 +13,7 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { allowAny?: boolean; allowedPromiseNames?: string[]; @@ -23,7 +23,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..67ab3c005887 --- /dev/null +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -0,0 +1,585 @@ +import type { + ParserServices, + TSESLint, + TSESTree, +} from '@typescript-eslint/utils'; + +import { + ImplicitLibVariable, + ScopeType, +} from '@typescript-eslint/scope-manager'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as tsutils from 'ts-api-utils'; +import * as ts from 'typescript'; + +import { + createRule, + findVariable, + getParserServices, + isNodeInside, +} from '../util'; + +export type MessageId = 'requireTypeExport' | 'requireTypeQueryExport'; + +export default createRule({ + 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 exports, so it should also be exported.', + requireTypeQueryExport: + '`typeof {{ name }}` is used in exports, so `{{ name }}` and/or a standalone `typeof {{ name }}` should also be exported.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const externalizedTypes = new Set(); + const reportedNodes = new Set(); + + function collectImportedTypes( + node: + | TSESTree.ImportDefaultSpecifier + | TSESTree.ImportNamespaceSpecifier + | TSESTree.ImportSpecifier, + ) { + externalizedTypes.add(node.local.name); + } + + function collectExportedTypes(node: TSESTree.Program) { + 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.ArrowFunctionExpression + | TSESTree.DefaultExportDeclarations + | TSESTree.ExportNamedDeclaration + ) & { + declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; + }, + ) { + checkNodeTypes(node.declaration); + } + + function visitExportedVariableDeclaration( + node: TSESTree.ExportNamedDeclaration & { + declaration: TSESTree.VariableDeclaration; + }, + ) { + for (const declaration of node.declaration.declarations) { + checkNodeTypes(declaration); + } + } + + function visitExportedTypeDeclaration( + node: TSESTree.ExportNamedDeclaration & { + declaration: + | TSESTree.TSInterfaceDeclaration + | TSESTree.TSTypeAliasDeclaration; + }, + ) { + checkNodeTypes(node.declaration); + } + + function visitExportDefaultDeclaration( + node: TSESTree.ExportDefaultDeclaration, + ) { + checkNodeTypes(node.declaration); + } + + function checkNodeTypes(node: TSESTree.Node) { + const { typeQueries, typeReferences } = getVisibleTypesRecursively( + node, + context.sourceCode, + getParserServices(context, true), + ); + + typeReferences.forEach(checkTypeReference); + typeQueries.forEach(checkTypeQuery); + } + + function checkTypeReference(node: TSESTree.TSTypeReference) { + const name = getTypeName(node.typeName); + if (externalizedTypes.has(name)) { + return; + } + + reportIfNeeded(name, node, 'requireTypeExport'); + } + + function checkTypeQuery(node: TSESTree.TSTypeQuery) { + if (node.exprName.type === AST_NODE_TYPES.TSImportType) { + return; + } + + const nameQueried = getTypeName(node.exprName); + reportIfNeeded(nameQueried, node, 'requireTypeQueryExport'); + } + + function reportIfNeeded( + name: string, + node: TSESTree.Node, + messageId: MessageId, + ) { + const declaration = findVariable(context.sourceCode.getScope(node), name) + ?.identifiers[0]; + + if (!declaration || reportedNodes.has(declaration)) { + return; + } + + reportedNodes.add(declaration); + + if ( + isDeclarationExported(declaration, getParserServices(context, true)) + ) { + return; + } + + context.report({ + node: declaration, + messageId, + data: { name }, + }); + } + + return { + ExportDefaultDeclaration: visitExportDefaultDeclaration, + 'ExportDefaultDeclaration[declaration.type="ArrowFunctionExpression"]': + visitExportedFunctionDeclaration, + 'ExportDefaultDeclaration[declaration.type="FunctionDeclaration"]': + visitExportedFunctionDeclaration, + 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': + visitExportedFunctionDeclaration, + 'ExportNamedDeclaration[declaration.type="TSDeclareFunction"]': + visitExportedFunctionDeclaration, + 'ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedTypeDeclaration, + 'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedTypeDeclaration, + 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"]': + visitExportedTypeDeclaration, + 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedTypeDeclaration, + 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': + visitExportedVariableDeclaration, + 'ImportDeclaration ImportDefaultSpecifier': collectImportedTypes, + 'ImportDeclaration ImportNamespaceSpecifier': collectImportedTypes, + 'ImportDeclaration ImportSpecifier': collectImportedTypes, + Program: collectExportedTypes, + }; + }, +}); + +function getLeftmostIdentifier( + node: TSESTree.EntityName | TSESTree.TSImportType | TSESTree.TSTypeReference, +) { + switch (node.type) { + case AST_NODE_TYPES.Identifier: + return node.name; + + case AST_NODE_TYPES.TSQualifiedName: + return getLeftmostIdentifier(node.left); + + default: + return undefined; + } +} + +function getTypeName(node: TSESTree.EntityName): string { + switch (node.type) { + case AST_NODE_TYPES.Identifier: + return node.name; + + case AST_NODE_TYPES.TSQualifiedName: + // Namespaced types such as enums are not exported directly, + // so we check the leftmost part of the name. + return getTypeName(node.left); + + case AST_NODE_TYPES.ThisExpression: + return 'this'; + } +} + +interface VisibleTypes { + typeReferences: Set; + typeQueries: Set; +} + +function getVisibleTypesRecursively( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, + services: ParserServices, +): VisibleTypes { + const typeReferences = new Set(); + const typeQueries = new Set(); + const visited = new Set(); + + collect(node); + + function collect(child: TSESTree.Node | null | undefined) { + if (!child || visited.has(child)) { + return; + } + + visited.add(child); + + switch (child.type) { + case AST_NODE_TYPES.VariableDeclarator: + collect(child.id); + collect(child.init); + break; + + case AST_NODE_TYPES.Identifier: { + collect(child.typeAnnotation?.typeAnnotation); + + // Resolve the variable to its declaration (in cases where the variable is referenced) + const scope = sourceCode.getScope(child); + const variableNode = findVariable(scope, child.name); + + variableNode?.defs.forEach(def => { + collect(def.name); + collect(def.node); + }); + break; + } + + case AST_NODE_TYPES.ObjectExpression: + child.properties.forEach(property => { + const nodeToCheck = + property.type === AST_NODE_TYPES.Property + ? property.value + : property.argument; + + collect(nodeToCheck); + }); + break; + + case AST_NODE_TYPES.ArrayExpression: + child.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(child.callee); + child.typeArguments?.params.forEach(collect); + break; + + case AST_NODE_TYPES.BinaryExpression: + case AST_NODE_TYPES.LogicalExpression: + collect(child.left); + collect(child.right); + break; + + case AST_NODE_TYPES.ConditionalExpression: + collect(child.consequent); + collect(child.alternate); + break; + + case AST_NODE_TYPES.ArrowFunctionExpression: + case AST_NODE_TYPES.FunctionDeclaration: + case AST_NODE_TYPES.FunctionExpression: + case AST_NODE_TYPES.TSDeclareFunction: + child.typeParameters?.params.forEach(param => + collect(param.constraint), + ); + child.params.forEach(collect); + collect(child.returnType?.typeAnnotation); + + if (child.body) { + collectFunctionReturnStatements(child, services).forEach(collect); + } + break; + + case AST_NODE_TYPES.AssignmentPattern: + collect(child.left); + break; + + case AST_NODE_TYPES.RestElement: + collect(child.argument); + collect(child.typeAnnotation?.typeAnnotation); + break; + + case AST_NODE_TYPES.ObjectPattern: + child.properties.forEach(collect); + collect(child.typeAnnotation?.typeAnnotation); + break; + + case AST_NODE_TYPES.ArrayPattern: + child.elements.forEach(collect); + collect(child.typeAnnotation?.typeAnnotation); + + break; + + case AST_NODE_TYPES.ReturnStatement: + collect(child.argument); + break; + + case AST_NODE_TYPES.TSTypeReference: { + const scope = sourceCode.getScope(child); + const variable = findVariable(scope, getTypeName(child.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(child); + } + + child.typeArguments?.params.forEach(collect); + break; + } + + case AST_NODE_TYPES.TSTypeOperator: + collect(child.typeAnnotation); + break; + + case AST_NODE_TYPES.TSTypeQuery: + if ( + isInsideFunctionDeclaration(child) && + !isReferencedNameInside(child.exprName, node, sourceCode) + ) { + typeQueries.add(child); + } + + break; + + case AST_NODE_TYPES.TSArrayType: + collect(child.elementType); + break; + + case AST_NODE_TYPES.TSTupleType: + child.elementTypes.forEach(collect); + break; + + case AST_NODE_TYPES.TSUnionType: + case AST_NODE_TYPES.TSIntersectionType: + child.types.forEach(collect); + break; + + case AST_NODE_TYPES.TSTypeLiteral: + child.members.forEach(collect); + break; + + case AST_NODE_TYPES.TSTemplateLiteralType: + child.types.forEach(collect); + break; + + case AST_NODE_TYPES.TSTypeAliasDeclaration: + collect(child.typeAnnotation); + break; + + case AST_NODE_TYPES.TSInterfaceDeclaration: + child.body.body.forEach(collect); + break; + + case AST_NODE_TYPES.TSPropertySignature: + collect(child.typeAnnotation?.typeAnnotation); + break; + + case AST_NODE_TYPES.TSQualifiedName: + collect(child.parent); + break; + + case AST_NODE_TYPES.TSAsExpression: + collect(child.expression); + collect(child.typeAnnotation); + break; + + case AST_NODE_TYPES.TSIndexedAccessType: + collect(child.objectType); + collect(child.indexType); + break; + } + } + + return { + typeQueries, + typeReferences, + }; +} + +const collectibleNodeTypes = new Set([ + AST_NODE_TYPES.TSTypeAliasDeclaration, + AST_NODE_TYPES.TSInterfaceDeclaration, + AST_NODE_TYPES.TSEnumDeclaration, + AST_NODE_TYPES.TSModuleDeclaration, +]); + +function isCollectableType( + node: TSESTree.Node, +): node is + | TSESTree.TSEnumDeclaration + | TSESTree.TSInterfaceDeclaration + | TSESTree.TSModuleDeclaration + | TSESTree.TSTypeAliasDeclaration { + return collectibleNodeTypes.has(node.type); +} + +const exportNodeTypes = new Set([ + AST_NODE_TYPES.ExportDefaultDeclaration, + AST_NODE_TYPES.ExportNamedDeclaration, + AST_NODE_TYPES.ExportAllDeclaration, +]); + +function isDeclarationExported( + declaration: TSESTree.Node & { parent: TSESTree.Node }, + services: ParserServices, +) { + if (exportNodeTypes.has(declaration.parent.type)) { + return true; + } + + if ( + declaration.parent.type === AST_NODE_TYPES.Program || + tsutils.isBlockLike(services.esTreeNodeToTSNodeMap.get(declaration.parent)) + ) { + return false; + } + + return isDeclarationExported(declaration.parent, services); +} + +const functionNodeTypes = new Set([ + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.TSDeclareFunction, +]); + +function isInsideFunctionDeclaration(node: TSESTree.Node): boolean { + if (!node.parent) { + return false; + } + + if (functionNodeTypes.has(node.parent.type)) { + return true; + } + + return isInsideFunctionDeclaration(node.parent); +} + +function getDeclarationForName( + node: TSESTree.Node, + name: string, + sourceCode: TSESLint.SourceCode, +) { + return sourceCode.getScope(node).set.get(name)?.identifiers.at(0); +} + +function isReferencedNameInside( + child: TSESTree.EntityName | TSESTree.TSImportType, + parent: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +) { + const localName = getLeftmostIdentifier(child); + if (!localName) { + return false; + } + + const declaration = getDeclarationForName(child, localName, sourceCode); + + return !!declaration && isNodeInside(declaration, parent); +} + +// TODO: This will have to use type information :( +function collectFunctionReturnStatements( + functionNode: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression, + services: ParserServices, +): 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 306342754018..f7b8e2f6a15d 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -12,7 +12,7 @@ import { isTypeFlagSet, } from '../util'; -type Options = [ +export type Options = [ { allowAny?: boolean; allowBoolean?: boolean; @@ -23,7 +23,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 3049c1fe3fcf..06c071520c0e 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -56,13 +56,13 @@ const optionTesters = ( option: `allow${type}` as const, tester, })); -type Options = [ +export type Options = [ { allow?: TypeOrValueSpecifier[]; } & Partial>, ]; -type MessageId = 'invalidType'; +export type MessageId = 'invalidType'; export default createRule({ name: 'restrict-template-expressions', diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 1323344605c8..316770b6b131 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -23,7 +23,7 @@ interface SwitchMetadata { readonly symbolName: string | undefined; } -type Options = [ +export type Options = [ { /** * If `true`, allow `default` cases on switch statements with exhaustive @@ -54,7 +54,7 @@ type Options = [ }, ]; -type MessageIds = +export type MessageIds = | 'addMissingCases' | 'dangerousDefaultCase' | 'switchIsNotExhaustive'; diff --git a/packages/eslint-plugin/src/rules/triple-slash-reference.ts b/packages/eslint-plugin/src/rules/triple-slash-reference.ts index d7908e7273b0..79c40ac37782 100644 --- a/packages/eslint-plugin/src/rules/triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/triple-slash-reference.ts @@ -4,14 +4,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/typedef.ts b/packages/eslint-plugin/src/rules/typedef.ts index dd70a97706ec..a1b61c00c20a 100644 --- a/packages/eslint-plugin/src/rules/typedef.ts +++ b/packages/eslint-plugin/src/rules/typedef.ts @@ -4,7 +4,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -const enum OptionKeys { +export const enum OptionKeys { ArrayDestructuring = 'arrayDestructuring', ArrowParameter = 'arrowParameter', MemberVariableDeclaration = 'memberVariableDeclaration', @@ -15,9 +15,9 @@ const enum OptionKeys { VariableDeclarationIgnoreFunction = 'variableDeclarationIgnoreFunction', } -type Options = Partial>; +export type Options = Partial>; -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 37e8d0868e25..455f0e66bcee 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -16,7 +16,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 130c56529a73..9c255b6f9f50 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -53,12 +53,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 0ee671556d13..42d311644dfd 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 @@ -14,7 +14,7 @@ import { nullThrows, } from '../util'; -type MessageIds = +export type MessageIds = | 'addUnknownRestTypeAnnotationSuggestion' | 'addUnknownTypeAnnotationSuggestion' | 'useUnknown' diff --git a/packages/eslint-plugin/src/util/astUtils.ts b/packages/eslint-plugin/src/util/astUtils.ts index c2450ae30130..4395056c6dfd 100644 --- a/packages/eslint-plugin/src/util/astUtils.ts +++ b/packages/eslint-plugin/src/util/astUtils.ts @@ -7,6 +7,13 @@ import { escapeRegExp } from './escapeRegExp'; // deeply re-export, for convenience export * from '@typescript-eslint/utils/ast-utils'; +export function isNodeInside( + child: TSESTree.Node, + parent: TSESTree.Node, +): boolean { + return child.range[0] > parent.range[0] && child.range[1] < parent.range[1]; +} + // The following is copied from `eslint`'s source code since it doesn't exist in eslint@5. // https://github.com/eslint/eslint/blob/145aec1ab9052fbca96a44d04927c595951b1536/lib/rules/utils/ast-utils.js#L1751-L1779 // Could be export { getNameLocationInGlobalDirectiveComment } from 'eslint/lib/rules/utils/ast-utils' diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index 97bc8620b01d..e895bcb3c7bd 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'); 'consistent-return': typeof import('eslint/lib/rules/consistent-return'); @@ -27,7 +27,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 49ab2b742dd9..ae010cad08d6 100644 --- a/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts +++ b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts @@ -6,7 +6,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 950708015d89..aa4a0befb109 100644 --- a/packages/eslint-plugin/src/util/getOperatorPrecedence.ts +++ b/packages/eslint-plugin/src/util/getOperatorPrecedence.ts @@ -296,7 +296,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 26afcaa6405b..478cda934910 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -6,7 +6,7 @@ import { ESLintUtils, } from '@typescript-eslint/utils'; -interface WrappingFixerParams { +export interface WrappingFixerParams { /** * Descendant of `node` we want to preserve. * Use this to replace some code with another. 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..34ec8eeaab0c --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-types-exports.shot @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 1`] = ` +"Incorrect + +interface Fruit { + ~~~~~ \`Fruit\` is used in exports, so it should also be exported. + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 2`] = ` +"Incorrect + +const fruits = { + ~~~~~~ \`typeof fruits\` is used in exports, so \`fruits\` and/or a standalone \`typeof fruits\` should also be exported. + apple: '🍏', + banana: '🍌', +}; + +export const getFruit = (key: keyof typeof fruits) => fruits[key]; +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 3`] = ` +"Incorrect + +enum Color { + ~~~~~ \`Color\` is used in exports, so it should also be exported. + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 4`] = ` +"Correct + +export interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 5`] = ` +"Correct + +export const fruits = { + apple: '🍏', + banana: '🍌', +}; + +export const getFruit = (key: keyof typeof fruits) => fruits[key]; +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 6`] = ` +"Correct + +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 3025252d72e6..0faf3c30e892 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts @@ -82,7 +82,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 = (): 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 e5883c31219d..18e1b25b17f8 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: { mutateCode?: MutateFn; mutateDeclaration?: 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..3a4ba68f6bbc --- /dev/null +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -0,0 +1,3472 @@ +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({ + languageOptions: { + parserOptions: { + project: './tsconfig-with-dom.json', + tsconfigRootDir: rootPath, + }, + }, +}); + +ruleTester.run('require-types-exports', rule, { + valid: [ + 'const someValue = undeclared;', + 'let someValue = undeclared;', + 'let someValue = a;', + 'let someValue = a();', + 'a();', + 'a.b();', + 'a[b]();', + "a['b']();", + "a['b'](c);", + 'export const a = () => b;', + 'export const a = () => b[0];', + 'export const a = () => [b];', + 'export const a = () => [, b];', + 'export const a = () => [b, ,];', + 'export const a = () => ({});', + 'export const a = () => ({ a });', + 'export const a = () => ({ a: a });', + 'export const a = () => ({ a: b });', + + '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 function f(...args): void {}', + 'export const f = (...args): void => {};', + + 'export default function f(): void {}', + 'export default (): void => {};', + + ` + function f(a: A): A { + return a; + } + `, + ` + 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 {} + `, + ` + 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 } => {}; + `, + ` + export type A = number; + export const f = ({ a }: { a: A }): void => {}; + `, + ` + 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 class Wrapper { + work(other: typeof this) {} + } + `, + 'export function noop(x: this) {}', + 'export function noop(x: typeof 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; + } + `, + ` +declare function wrap(listeners: unknown): unknown; + +type Abc = 'abc'; + +export default wrap({ + abc(input: Abc) { + // + }, +}); + `, + 'export function example(config: string): typeof config {}', + 'export function example(config: string): typeof config.length {}', + "export function example(config: string): (typeof config)['length'] {}", + "export function example(config: string): typeof import('config') {}", + 'export function example(config: ExternalGlobal) {}', + 'export function example(config: typeof ExternalGlobal) {}', + 'export function example(config: typeof ExternalGlobal.length) {}', + "export function example(config: (typeof ExternalGlobal)['length']) {}", + ` +export namespace Values { + export type Fruit = 'apple'; + + export function logFruit(fruit: Fruit) { + console.log(fruit); + } +} + `, + ` +declare module '@babel/eslint-parser' { + export interface Options {} + export function parse(options: Options): void; +} + `, + ` +export const pairs = { KEY: 'value' } as const; + +export function emitDeprecationWarning(akey: keyof typeof pairs) { + console.log(key); +} + `, + ` +declare function identity(input: T): T; + +interface Box { + // ... +} + +export function usesType() { + const box: Box = {}; + return identity(box); +} + `, + ], + invalid: [ + { + code: ` + type Arg = number; + + export function f(a: Arg): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export const f = (a: Arg): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export default function (a: Arg): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export default (a: Arg): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export function f(a: Arg, b: Arg): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export const f = (a: Arg, b: Arg): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1, b: Arg2): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1, b: Arg2): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + + interface Arg2 { + a: string; + } + + export function f(a: Arg1, b: Arg2): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 19, + data: { + name: 'Arg2', + }, + endColumn: 23, + line: 4, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + + interface Arg2 { + a: string; + } + + export const f = (a: Arg1, b: Arg2): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 19, + data: { + name: 'Arg2', + }, + endColumn: 23, + line: 4, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1 | Arg2): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1 | Arg2): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1 & Arg2): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1 & Arg2): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f([a, b]: [Arg1, Arg2, number]): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + type Arg3 = boolean; + + export function f([a, b]: [Arg1, Arg2, number], c: Arg3): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg3', + }, + endColumn: 18, + line: 4, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = ([a, b]: [Arg1, Arg2, number]): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f({ a, b }: { a: Arg1; b: Arg2; c: number }): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = ({ a, b }: { a: Arg1; b: Arg2; c: number }): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export function f(...args: Arg[]): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export const f = (...args: Arg[]): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export function f(a: Arg = 1): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export const f = (a: Arg = 1): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + export function f(a: Fruit): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + export const f = (a: Fruit): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export function f(a: T): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: T): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = string; + + export function f(a: T): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = string; + + export function f(a: T): void {} + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret = string; + + export function f(): Ret {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret = string; + + export const f = (): Ret => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Ret', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): Ret1 | Ret2 {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): Ret1 | Ret2 => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): Ret1 & Ret2 {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): Ret1 & Ret2 => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): [Ret1, Ret2, number, Ret1] {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): [Ret1, Ret2, number, Ret1] => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): { a: Ret1; b: Ret2; c: number; d: Ret1 } {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): { a: Ret1; b: Ret2; c: number; d: Ret1 } => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret = string; + + export function f(): T {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Ret = string; + + export function f(): T {} + `, + errors: [ + { + column: 14, + data: { + name: 'Ret', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + const a = (a: Arg): void => {}; + + export default a; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + const a = function (a: Arg): void {}; + + export default a; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export declare function f(a: Arg): void; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg = number; + + export declare function f(a: Arg): Arg; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg', + }, + endColumn: 17, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type R = number; + + export function f() { + const value: { num: R } = { + num: 1, + }; + + return value; + } + `, + errors: [ + { + column: 14, + data: { + name: 'R', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Ret', + }, + endColumn: 17, + line: 4, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f1 = (a: Arg1): void => {}, + f2 = (a: Arg2): void => {}; + `, + errors: [ + { + column: 14, + data: { + name: 'Arg1', + }, + endColumn: 18, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Arg2', + }, + endColumn: 18, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + namespace A { + export namespace B { + export type C = number; + } + } + + export function a(arg: A.B.C) { + return arg; + } + `, + errors: [ + { + column: 19, + data: { + name: 'A', + }, + endColumn: 20, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + namespace A { + export type B = number; + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 6, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + namespace A { + export interface B { + value: number; + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 8, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + namespace A { + export enum B { + Value1, + Value2, + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 9, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + namespace A { + export namespace B { + export type C = number; + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 8, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'T1', + }, + endColumn: 16, + line: 4, + messageId: 'requireTypeExport', + }, + { + column: 19, + data: { + name: 'T2', + }, + endColumn: 21, + line: 6, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'T1', + }, + endColumn: 16, + line: 4, + messageId: 'requireTypeExport', + }, + { + column: 19, + data: { + name: 'T2', + }, + endColumn: 21, + line: 6, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'T1', + }, + endColumn: 16, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 19, + data: { + name: 'T2', + }, + endColumn: 21, + line: 4, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'T3', + }, + endColumn: 16, + line: 8, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = string; + type B = string; + + const apple: A = 'apple'; + const banana: B = 'banana'; + + export const value = { + path: { + to: { + apple, + and: { + banana, + }, + }, + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana], + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana] as const, + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana] as any, + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple = 'apple'; + const banana = 'banana'; + + export const value = { + path: { + to: [apple, banana] as [Fruit, Fruit], + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple = 'apple'; + const banana = 'banana'; + + export const value = { + path: { + to: [apple, banana] as Fruit | number, + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'C', + }, + endColumn: 15, + line: 4, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'D', + }, + endColumn: 15, + line: 5, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + type B = string; + + export const value = { + func: (arg: A): B => 'apple', + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + type B = string; + + export const value = { + func: function (arg: A): B { + return 'apple'; + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + type B = string; + + const func = (arg: A): B => 'apple'; + + export const value = { + func, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + type B = string; + + const func = function (arg: A): B { + return 'apple'; + }; + + export const value = { + func, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + const func = (arg: T): T => 'apple'; + + export const value = { + func, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + const func = function (arg: T): T { + return 'apple'; + }; + + export const value = { + func, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + export const value = { + func: (arg: T): T => 'apple', + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + export const value = { + func: function (arg: T): T { + return 'apple'; + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + declare function func(arg: T): T; + + export const value = { + func, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare function func(arg: T): T; + + export const value = { + func, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + a, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + key: () => a, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + key: function () { + return a; + }, + }; + `, + errors: [ + { + column: 14, + data: { + name: 'Fruit', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { name: 'Item' }, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { name: 'ItemKey' }, + line: 7, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: (() => item)(), + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => a)(item), + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: T) => a)(item), + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => [a])(item), + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => ({ a }))(item), + }; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + export function func1(arg: R): R { + return func2(arg); + } + + declare function func2(arg: T): T; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + type B = number; + + export function func1(arg: R) { + return func2(arg); + } + + declare function func2(arg: B): B; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'C', + }, + endColumn: 15, + line: 4, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + type B = number; + type C = number; + + export function func1(arg: R) { + switch (Math.random()) { + case 0: + return func2(arg); + case 1: + return func3(arg); + } + } + + declare function func2(arg: B): B; + declare function func3(arg: C): C; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'C', + }, + endColumn: 15, + line: 4, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + type B = number; + + export function func1(arg: R) { + return arg as B; + } + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + type B = string; + + export function func1(arg: R): R { + function doWork(arg2: B): void {} + + doWork(String(arg)); + + return arg; + } + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type ItemsMap = Record; + type Key = keyof ItemsMap; + + export function get(key: K): ItemsMap[K] { + return key as never; + } + `, + errors: [ + { + column: 14, + data: { + name: 'ItemsMap', + }, + endColumn: 22, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Key', + }, + endColumn: 17, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + const value: A = 1; + + export function func() { + return Math.random() > 0.5 && value; + } + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + 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: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + declare function func(): string; + + type A = string; + + export default func(); + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 4, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type Apple = 'apple'; + type Banana = 'banana'; + + export type Fruits = Apple | Banana; + `, + errors: [ + { + column: 14, + data: { + name: 'Apple', + }, + endColumn: 19, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 14, + data: { + name: 'Banana', + }, + endColumn: 20, + line: 3, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + export interface B { + a: A; + } + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = number; + + interface B { + b: string; + } + + export namespace C { + export type D = A; + export type E = B; + } + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + { + column: 19, + data: { + name: 'B', + }, + endColumn: 20, + line: 4, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + type A = 'test'; + export type B = \`test-\${A}\`; + `, + errors: [ + { + column: 14, + data: { + name: 'A', + }, + endColumn: 15, + line: 2, + messageId: 'requireTypeExport', + }, + ], + }, + { + code: ` + const fruits = { apple: 'apple' }; + + export function getFruit( + key: Key, + ): (typeof fruits)[Key] { + return fruits[key]; + } + `, + errors: [ + { + column: 15, + data: { + name: 'fruits', + }, + endColumn: 21, + line: 2, + messageId: 'requireTypeQueryExport', + }, + ], + }, + { + code: ` + const fruits = { apple: 'apple' }; + + export declare function processFruit( + fruit: F, + ): void; + `, + errors: [ + { + column: 15, + data: { + name: 'fruits', + }, + endColumn: 21, + line: 2, + messageId: 'requireTypeQueryExport', + }, + ], + }, + { + code: ` + const fruits = { apple: 'apple' }; + + export declare function processFruit< + F extends Record, + >(fruit: F): void; + `, + errors: [ + { + column: 15, + data: { + name: 'fruits', + }, + endColumn: 21, + line: 2, + messageId: 'requireTypeQueryExport', + }, + ], + }, + ], +}); 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/rule-tester/src/types/index.ts b/packages/rule-tester/src/types/index.ts index becf16d2c5c9..a484d24f5000 100644 --- a/packages/rule-tester/src/types/index.ts +++ b/packages/rule-tester/src/types/index.ts @@ -2,7 +2,7 @@ import type { InvalidTestCase } from './InvalidTestCase'; import type { RuleTesterConfig } from './RuleTesterConfig'; import type { ValidTestCase } from './ValidTestCase'; -type Mutable = { +export type Mutable = { -readonly [P in keyof T]: T[P]; }; export type TesterConfigWithDefaults = Mutable< diff --git a/packages/rule-tester/src/utils/SourceCodeFixer.ts b/packages/rule-tester/src/utils/SourceCodeFixer.ts index 6a108c2e21ec..accb15767a4b 100644 --- a/packages/rule-tester/src/utils/SourceCodeFixer.ts +++ b/packages/rule-tester/src/utils/SourceCodeFixer.ts @@ -4,7 +4,7 @@ import type { Linter } from '@typescript-eslint/utils/ts-eslint'; import { hasOwnProperty } from './hasOwnProperty'; -type LintMessage = Linter.LintMessage | Linter.LintSuggestion; +export type LintMessage = Linter.LintMessage | Linter.LintSuggestion; type LintMessageWithFix = LintMessage & Required>; const BOM = '\uFEFF'; diff --git a/packages/rule-tester/src/utils/config-validator.ts b/packages/rule-tester/src/utils/config-validator.ts index bc5e09e7646c..50f73522b3d0 100644 --- a/packages/rule-tester/src/utils/config-validator.ts +++ b/packages/rule-tester/src/utils/config-validator.ts @@ -18,7 +18,7 @@ import { flatConfigSchema } from './flat-config-schema'; import { getRuleOptionsSchema } from './getRuleOptionsSchema'; import { hasOwnProperty } from './hasOwnProperty'; -type GetAdditionalRule = (ruleId: string) => AnyRuleModule | null; +export type GetAdditionalRule = (ruleId: string) => AnyRuleModule | null; const ajv = ajvBuilder(); const ruleValidators = new WeakMap(); diff --git a/packages/rule-tester/src/utils/deprecation-warnings.ts b/packages/rule-tester/src/utils/deprecation-warnings.ts index 2453be707f45..230e7a4aed83 100644 --- a/packages/rule-tester/src/utils/deprecation-warnings.ts +++ b/packages/rule-tester/src/utils/deprecation-warnings.ts @@ -3,7 +3,7 @@ import path from 'node:path'; // Definitions for deprecation warnings. -const deprecationWarningMessages = { +export const deprecationWarningMessages = { ESLINT_LEGACY_ECMAFEATURES: "The 'ecmaFeatures' config file property is deprecated and has no effect.", } as const; diff --git a/packages/rule-tester/src/utils/flat-config-schema.ts b/packages/rule-tester/src/utils/flat-config-schema.ts index 1b631070a3e0..cacd9eb35381 100644 --- a/packages/rule-tester/src/utils/flat-config-schema.ts +++ b/packages/rule-tester/src/utils/flat-config-schema.ts @@ -7,9 +7,9 @@ import type { import { normalizeSeverityToNumber } from './severity'; -type PluginMemberName = `${string}/${string}`; +export type PluginMemberName = `${string}/${string}`; -interface ObjectPropertySchema { +export interface ObjectPropertySchema { merge: string | ((a: T, b: T) => T); validate: string | ((value: unknown) => asserts value is T); } @@ -423,7 +423,7 @@ const processorSchema: ObjectPropertySchema = { }, }; -type ConfigRules = Record; +export type ConfigRules = Record; const rulesSchema = { merge(first: ConfigRules = {}, second: ConfigRules = {}): ConfigRules { diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index cd16389445a5..075bee1840e8 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -161,6 +161,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/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts index eeb80399882c..8ab7a8df71f6 100644 --- a/packages/typescript-eslint/src/configs/disable-type-checked.ts +++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts @@ -76,6 +76,6 @@ export default ( '@typescript-eslint/use-unknown-in-catch-callback-variable': 'off', }, languageOptions: { - parserOptions: { project: false, program: null, projectService: false }, + parserOptions: { program: null, project: false, projectService: false }, }, }); diff --git a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts index c9efc88da6c3..ef29e1006bc4 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts @@ -74,10 +74,10 @@ export default ( { allowAny: false, allowBoolean: false, + allowNever: false, allowNullish: false, allowNumber: false, allowRegExp: false, - allowNever: false, }, ], '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 4f354baf7e40..e9481d6050a6 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -92,6 +92,7 @@ export default ( '@typescript-eslint/related-getter-setter-pairs': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': [ 'error', { @@ -107,10 +108,10 @@ export default ( { allowAny: false, allowBoolean: false, + allowNever: false, allowNullish: false, allowNumber: false, allowRegExp: false, - allowNever: false, }, ], 'no-return-await': 'off', diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index 0afa85b9f088..ab3f8aa2388e 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -56,6 +56,7 @@ export default ( '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unified-signatures': 'error', }, diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index 943ee4c95079..9793f4762773 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -11,7 +11,7 @@ const isAtLeast50 = typescriptVersionIsAtLeast['5.0']; const SyntaxKind = ts.SyntaxKind; -type LogicalOperatorKind = +export type LogicalOperatorKind = | ts.SyntaxKind.AmpersandAmpersandToken | ts.SyntaxKind.BarBarToken | ts.SyntaxKind.QuestionQuestionToken; @@ -31,7 +31,7 @@ interface TokenToText [SyntaxKind.UniqueKeyword]: 'unique'; } -type AssignmentOperatorKind = keyof TSESTree.AssignmentOperatorToText; +export type AssignmentOperatorKind = keyof TSESTree.AssignmentOperatorToText; const ASSIGNMENT_OPERATORS: ReadonlySet = new Set([ ts.SyntaxKind.AmpersandAmpersandEqualsToken, ts.SyntaxKind.AmpersandEqualsToken, @@ -51,7 +51,7 @@ const ASSIGNMENT_OPERATORS: ReadonlySet = new Set([ ts.SyntaxKind.SlashEqualsToken, ]); -type BinaryOperatorKind = keyof TSESTree.BinaryOperatorToText; +export type BinaryOperatorKind = keyof TSESTree.BinaryOperatorToText; const BINARY_OPERATORS: ReadonlySet = new Set([ SyntaxKind.AmpersandAmpersandToken, SyntaxKind.AmpersandToken, @@ -79,7 +79,7 @@ const BINARY_OPERATORS: ReadonlySet = new Set([ SyntaxKind.SlashToken, ]); -type DeclarationKind = TSESTree.VariableDeclaration['kind']; +export type DeclarationKind = TSESTree.VariableDeclaration['kind']; /** * Returns true if the given ts.Token is the assignment operator @@ -107,9 +107,8 @@ export function isESTreeBinaryOperator( return (BINARY_OPERATORS as ReadonlySet).has(operator.kind); } -type TokenForTokenKind = T extends keyof TokenToText - ? TokenToText[T] - : string | undefined; +export type TokenForTokenKind = + T extends keyof TokenToText ? TokenToText[T] : string | undefined; /** * Returns the string form of the given TSToken SyntaxKind */ diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 1ea208210e0a..6f2039de080d 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -5,7 +5,7 @@ import type { CanonicalPath } from '../create-program/shared'; import type { TSESTree } from '../ts-estree'; import type { CacheLike } from './ExpiringCache'; -type DebugModule = 'eslint' | 'typescript' | 'typescript-eslint'; +export type DebugModule = 'eslint' | 'typescript' | 'typescript-eslint'; // Workaround to support new TS version features for consumers on old TS versions declare module 'typescript' { diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 48cbf0b8da1c..77a8e29b28e3 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -111,7 +111,7 @@ interface ParseOptions { suppressDeprecatedPropertyWarnings?: boolean; } -interface ParseAndGenerateServicesOptions extends ParseOptions { +export interface ParseAndGenerateServicesOptions extends ParseOptions { /** * Granular control of the expiry lifetime of our internal caches. * You can specify the number of seconds as an integer number, or the string diff --git a/packages/typescript-estree/src/simple-traverse.ts b/packages/typescript-estree/src/simple-traverse.ts index aa0d7ed4d070..fc64de4ec040 100644 --- a/packages/typescript-estree/src/simple-traverse.ts +++ b/packages/typescript-estree/src/simple-traverse.ts @@ -21,7 +21,7 @@ function getVisitorKeysForNode( return (keys ?? []) as never; } -type SimpleTraverseOptions = Readonly< +export type SimpleTraverseOptions = Readonly< | { enter: (node: TSESTree.Node, parent: TSESTree.Node | undefined) => void; visitorKeys?: Readonly; diff --git a/packages/typescript-estree/tests/test-utils/test-utils.ts b/packages/typescript-estree/tests/test-utils/test-utils.ts index 3ea4e57ea175..35daef3f6f1f 100644 --- a/packages/typescript-estree/tests/test-utils/test-utils.ts +++ b/packages/typescript-estree/tests/test-utils/test-utils.ts @@ -82,7 +82,7 @@ export function deeplyCopy>(ast: T): T { return omitDeep(ast) as T; } -type UnknownObject = Record; +export type UnknownObject = Record; function isObjectLike(value: unknown): boolean { return ( diff --git a/packages/utils/src/ast-utils/eslint-utils/ReferenceTracker.ts b/packages/utils/src/ast-utils/eslint-utils/ReferenceTracker.ts index e18a01e21f1a..cdab1852d20d 100644 --- a/packages/utils/src/ast-utils/eslint-utils/ReferenceTracker.ts +++ b/packages/utils/src/ast-utils/eslint-utils/ReferenceTracker.ts @@ -41,7 +41,7 @@ interface ReferenceTracker { traceMap: ReferenceTracker.TraceMap, ): IterableIterator>; } -interface ReferenceTrackerStatic { +export interface ReferenceTrackerStatic { readonly CALL: typeof ReferenceTrackerCALL; readonly CONSTRUCT: typeof ReferenceTrackerCONSTRUCT; readonly ESM: typeof ReferenceTrackerESM; diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 3b281090606b..abbfc0f87a5f 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -154,7 +154,7 @@ interface ReportDescriptorBase { // we disallow this because it's much better to use messageIds for reusable errors that are easily testable // readonly desc?: string; } -interface ReportDescriptorWithSuggestion +export interface ReportDescriptorWithSuggestion extends ReportDescriptorBase { /** * 6.7's Suggestions API @@ -162,7 +162,7 @@ interface ReportDescriptorWithSuggestion readonly suggest?: Readonly> | null; } -interface ReportDescriptorNodeOptionalLoc { +export interface ReportDescriptorNodeOptionalLoc { /** * An override of the location of the report */ @@ -174,7 +174,7 @@ interface ReportDescriptorNodeOptionalLoc { */ readonly node: TSESTree.Node | TSESTree.Token; } -interface ReportDescriptorLocOnly { +export interface ReportDescriptorLocOnly { /** * An override of the location of the report */ @@ -425,7 +425,7 @@ export type RuleFunction = ( node: T, ) => void; -interface RuleListenerBaseSelectors { +export interface RuleListenerBaseSelectors { AccessorProperty?: RuleFunction; ArrayExpression?: RuleFunction; ArrayPattern?: RuleFunction; @@ -595,10 +595,13 @@ interface RuleListenerBaseSelectors { WithStatement?: RuleFunction; YieldExpression?: RuleFunction; } -type RuleListenerExitSelectors = { +export type RuleListenerExitSelectors = { [K in keyof RuleListenerBaseSelectors as `${K}:exit`]: RuleListenerBaseSelectors[K]; }; -type RuleListenerCatchAllBaseCase = Record; +export type RuleListenerCatchAllBaseCase = Record< + string, + RuleFunction | undefined +>; // Interface to merge into for anyone that wants to add more selectors // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface RuleListenerExtension { diff --git a/packages/website/src/components/FinancialContributors/Sponsor.tsx b/packages/website/src/components/FinancialContributors/Sponsor.tsx index 3f3ce6d8036f..614d8be95f95 100644 --- a/packages/website/src/components/FinancialContributors/Sponsor.tsx +++ b/packages/website/src/components/FinancialContributors/Sponsor.tsx @@ -5,7 +5,7 @@ import type { SponsorData } from './types'; import styles from './styles.module.css'; -interface SponsorProps { +export interface SponsorProps { includeName?: boolean; sponsor: SponsorData; } diff --git a/packages/website/src/components/FinancialContributors/Sponsors/index.tsx b/packages/website/src/components/FinancialContributors/Sponsors/index.tsx index 68fc7cac4bfe..c0967820006f 100644 --- a/packages/website/src/components/FinancialContributors/Sponsors/index.tsx +++ b/packages/website/src/components/FinancialContributors/Sponsors/index.tsx @@ -6,7 +6,7 @@ import type { SponsorData } from '../types'; import { Sponsor } from '../Sponsor'; import styles from './styles.module.css'; -interface SponsorsProps { +export interface SponsorsProps { className: string; expanded?: boolean; includeName?: boolean; diff --git a/packages/website/src/components/ast/tsUtils.ts b/packages/website/src/components/ast/tsUtils.ts index bc00c84c3306..7d002da97f4f 100644 --- a/packages/website/src/components/ast/tsUtils.ts +++ b/packages/website/src/components/ast/tsUtils.ts @@ -1,4 +1,4 @@ -interface TsParsedEnums { +export interface TsParsedEnums { LanguageVariant: Record; ModifierFlags: Record; NodeFlags: Record; diff --git a/packages/website/src/components/config/ConfigTypeScript.tsx b/packages/website/src/components/config/ConfigTypeScript.tsx index 16400a0aa2dd..8215b859ef45 100644 --- a/packages/website/src/components/config/ConfigTypeScript.tsx +++ b/packages/website/src/components/config/ConfigTypeScript.tsx @@ -8,7 +8,7 @@ import { getTypescriptOptions } from '../lib/jsonSchema'; import { shallowEqual } from '../lib/shallowEqual'; import ConfigEditor from './ConfigEditor'; -interface ConfigTypeScriptProps { +export interface ConfigTypeScriptProps { readonly className?: string; readonly config?: string; readonly onChange: (config: Partial) => void; diff --git a/packages/website/src/components/editor/loadSandbox.ts b/packages/website/src/components/editor/loadSandbox.ts index 1521e7cf5aa3..1aab845828a4 100644 --- a/packages/website/src/components/editor/loadSandbox.ts +++ b/packages/website/src/components/editor/loadSandbox.ts @@ -3,8 +3,8 @@ import type MonacoEditor from 'monaco-editor'; import type * as SandboxFactory from '../../vendor/sandbox'; import type { WebLinterModule } from '../linter/types'; -type Monaco = typeof MonacoEditor; -type Sandbox = typeof SandboxFactory; +export type Monaco = typeof MonacoEditor; +export type Sandbox = typeof SandboxFactory; export interface SandboxModel { lintUtils: WebLinterModule; diff --git a/packages/website/src/components/linter/bridge.ts b/packages/website/src/components/linter/bridge.ts index 414873484c1a..c4d17174748f 100644 --- a/packages/website/src/components/linter/bridge.ts +++ b/packages/website/src/components/linter/bridge.ts @@ -7,9 +7,11 @@ import type { PlaygroundSystem } from './types'; import { debounce } from '../lib/debounce'; import { getPathRegExp } from './utils'; +export type TSVFS = typeof tsvfs; + export function createFileSystem( config: Pick, - vfs: typeof tsvfs, + vfs: TSVFS, ): PlaygroundSystem { const files = new Map(); files.set(`/.eslintrc`, config.eslintrc); diff --git a/packages/website/src/components/linter/createLinter.ts b/packages/website/src/components/linter/createLinter.ts index 78f001439d87..449b847f5344 100644 --- a/packages/website/src/components/linter/createLinter.ts +++ b/packages/website/src/components/linter/createLinter.ts @@ -38,10 +38,12 @@ export interface CreateLinter { updateParserOptions(sourceType?: SourceType): void; } +export type TSVFS = typeof tsvfs; + export function createLinter( system: PlaygroundSystem, webLinterModule: WebLinterModule, - vfs: typeof tsvfs, + vfs: TSVFS, ): CreateLinter { const rules: CreateLinter['rules'] = new Map(); const configs = new Map(Object.entries(webLinterModule.configs)); diff --git a/packages/website/src/components/linter/createParser.ts b/packages/website/src/components/linter/createParser.ts index 6af8e0af3b85..9e2cf874bd3e 100644 --- a/packages/website/src/components/linter/createParser.ts +++ b/packages/website/src/components/linter/createParser.ts @@ -12,12 +12,14 @@ import type { import { defaultParseSettings } from './config'; +export type TSVFS = typeof tsvfs; + export function createParser( system: PlaygroundSystem, compilerOptions: ts.CompilerOptions, onUpdate: (filename: string, model: UpdateModel) => void, utils: WebLinterModule, - vfs: typeof tsvfs, + vfs: TSVFS, ): { updateConfig: (compilerOptions: ts.CompilerOptions) => void; } & Parser.ParserModule {