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 { pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy