From 9a0c28a9257fd73380dbdfa6366b0a9d6db22e66 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Mon, 12 Feb 2024 22:43:06 +0200 Subject: [PATCH 01/86] feat(eslint-plugin): [require-types-exports] add new rule Closes #7670 --- .../src/rules/require-types-exports.ts | 288 ++++ .../tests/rules/require-types-exports.test.ts | 1217 +++++++++++++++++ 2 files changed, 1505 insertions(+) create mode 100644 packages/eslint-plugin/src/rules/require-types-exports.ts create mode 100644 packages/eslint-plugin/tests/rules/require-types-exports.test.ts 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..a972eb707461 --- /dev/null +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -0,0 +1,288 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule } from '../util'; + +type MessageIds = 'requireTypeExport'; + +export default createRule<[], MessageIds>({ + name: 'require-types-exports', + meta: { + type: 'suggestion', + docs: { + recommended: 'strict', + description: + 'Require exporting types that are used in exported functions declarations', + }, + messages: { + requireTypeExport: 'Expected type "{{ name }}" to be exported', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const exportedTypes = new Set(); + const reported = new Set(); + + function visitExportedFunctionDeclaration( + node: TSESTree.ExportNamedDeclaration & { + declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; + }, + ): void { + checkFunctionParamsTypes(node.declaration); + checkFunctionReturnType(node.declaration); + } + + function visitExportedVariableDeclaration( + node: TSESTree.ExportNamedDeclaration & { + declaration: TSESTree.VariableDeclaration; + }, + ): void { + node.declaration.declarations.forEach(declaration => { + if (declaration.init?.type === AST_NODE_TYPES.ArrowFunctionExpression) { + checkFunctionParamsTypes(declaration.init); + checkFunctionReturnType(declaration.init); + } + }); + } + + function checkFunctionParamsTypes( + node: + | TSESTree.FunctionDeclaration + | TSESTree.TSDeclareFunction + | TSESTree.ArrowFunctionExpression, + ): void { + node.params.forEach(param => { + getParamTypesNodes(param).forEach(paramTypeNode => { + const name = getTypeName(paramTypeNode); + + if (!name) { + // TODO: Report on the whole function? + return; + } + + const isExported = exportedTypes.has(name); + const isReported = reported.has(name); + + if (isExported || isReported) { + return; + } + + context.report({ + node: paramTypeNode, + messageId: 'requireTypeExport', + data: { + name, + }, + }); + + reported.add(name); + }); + }); + } + + function checkFunctionReturnType( + node: + | TSESTree.FunctionDeclaration + | TSESTree.TSDeclareFunction + | TSESTree.ArrowFunctionExpression, + ): void { + const returnTypeNode = node.returnType; + + if (!returnTypeNode) { + return; + } + + getReturnTypesNodes(returnTypeNode).forEach(returnTypeNode => { + const name = getTypeName(returnTypeNode); + + if (!name) { + return; + } + + const isExported = exportedTypes.has(name); + const isReported = reported.has(name); + + if (isExported || isReported) { + return; + } + + context.report({ + node: returnTypeNode, + messageId: 'requireTypeExport', + data: { + name, + }, + }); + + reported.add(name); + }); + } + + function getParamTypesNodes( + param: TSESTree.Parameter, + ): TSESTree.TSTypeReference[] { + // Single type + if ( + param.type === AST_NODE_TYPES.Identifier && + param.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSTypeReference + ) { + return [param.typeAnnotation.typeAnnotation]; + } + + // Union or intersection + if ( + param.type === AST_NODE_TYPES.Identifier && + (param.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSUnionType || + param.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSIntersectionType) + ) { + return param.typeAnnotation.typeAnnotation.types.filter( + type => type.type === AST_NODE_TYPES.TSTypeReference, + ) as TSESTree.TSTypeReference[]; + } + + // Tuple + if ( + param.type === AST_NODE_TYPES.ArrayPattern && + param.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES.TSTupleType + ) { + return param.typeAnnotation.typeAnnotation.elementTypes.filter( + type => type.type === AST_NODE_TYPES.TSTypeReference, + ) as TSESTree.TSTypeReference[]; + } + + // Inline object + if ( + param.type === AST_NODE_TYPES.ObjectPattern && + param.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSTypeLiteral + ) { + return param.typeAnnotation.typeAnnotation.members.reduce< + TSESTree.TSTypeReference[] + >((acc, member) => { + if ( + member.type === AST_NODE_TYPES.TSPropertySignature && + member.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSTypeReference + ) { + acc.push(member.typeAnnotation.typeAnnotation); + } + + return acc; + }, []); + } + + // Rest params + if ( + param.type === AST_NODE_TYPES.RestElement && + param.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSArrayType && + param.typeAnnotation.typeAnnotation.elementType.type === + AST_NODE_TYPES.TSTypeReference + ) { + return [param.typeAnnotation.typeAnnotation.elementType]; + } + + // Default value assignment + if ( + param.type === AST_NODE_TYPES.AssignmentPattern && + param.left.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSTypeReference + ) { + return [param.left.typeAnnotation.typeAnnotation]; + } + + return []; + } + + function getReturnTypesNodes( + typeAnnotation: TSESTree.TSTypeAnnotation, + ): TSESTree.TSTypeReference[] { + // Single type + if ( + typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference + ) { + return [typeAnnotation.typeAnnotation]; + } + + // Union or intersection + if ( + typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSUnionType || + typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSIntersectionType + ) { + return typeAnnotation.typeAnnotation.types.filter( + type => type.type === AST_NODE_TYPES.TSTypeReference, + ) as TSESTree.TSTypeReference[]; + } + + // Tuple + if (typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSTupleType) { + return typeAnnotation.typeAnnotation.elementTypes.filter( + type => type.type === AST_NODE_TYPES.TSTypeReference, + ) as TSESTree.TSTypeReference[]; + } + + // Inline object + if (typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSTypeLiteral) { + return typeAnnotation.typeAnnotation.members.reduce< + TSESTree.TSTypeReference[] + >((acc, member) => { + if ( + member.type === AST_NODE_TYPES.TSPropertySignature && + member.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSTypeReference + ) { + acc.push(member.typeAnnotation.typeAnnotation); + } + + return acc; + }, []); + } + + return []; + } + + function collectExportedTypes(node: TSESTree.Program): void { + node.body.forEach(statement => { + if (statement.type !== AST_NODE_TYPES.ExportNamedDeclaration) { + return; + } + + const { declaration } = statement; + + if ( + declaration?.type === AST_NODE_TYPES.TSTypeAliasDeclaration || + declaration?.type === AST_NODE_TYPES.TSInterfaceDeclaration + ) { + exportedTypes.add(declaration.id.name); + + return; + } + }); + } + + function getTypeName(typeReference: TSESTree.TSTypeReference): string { + if (typeReference.typeName.type === AST_NODE_TYPES.Identifier) { + return typeReference.typeName.name; + } + + return ''; + } + + return { + Program: collectExportedTypes, + + 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': + visitExportedFunctionDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSDeclareFunction"]': + visitExportedFunctionDeclaration, + + 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': + visitExportedVariableDeclaration, + }; + }, +}); 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..f8d0437e27f7 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -0,0 +1,1217 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/require-types-exports'; +import { getFixturesRootDir } from '../RuleTester'; + +const rootPath = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, +}); + +ruleTester.run('require-types-exports', rule, { + valid: [ + 'export function f(): void {}', + 'export const f = (): void => {};', + + 'export function f(a: number): void {}', + 'export const f = (a: number): void => {};', + + 'export function f(a: any): void {}', + 'export const f = (a: any): void => {};', + + 'export function f(a: null): void {}', + 'export const f = (a: null): void => {};', + + 'export function f(a: string | number): void {}', + 'export const f = (a: string | number): void => {};', + + 'export function f(a?: string | number): void {}', + 'export const f = (a?: string | number): void => {};', + + 'export function f(a: number): string {}', + 'export const f = (a: number): string => {};', + + 'export function f(...args: any[]): void {}', + 'export const f = (...args: any[]): void => {};', + + 'export function f(...args: unknown[]): void {}', + 'export const f = (...args: unknown[]): void => {};', + + ` + type A = number; + function f(a: A): A { + return a; + } + `, + + ` + type A = number; + const f = (a: A): A => a; + `, + + ` + type A = number; + type B = string; + function f(a: A | B): any { + return a; + } + `, + + ` + type A = number; + type B = string; + const f = (a: A | B): any => a; + `, + + ` + type A = number; + declare function f(a: A): void; + `, + + ` + type A = number; + function f({ a }: { a: A }): A {} + `, + + ` + type A = number; + const f = ({ a }: { a: A }): A => {}; + `, + + ` + type A = number; + type B = string; + function f([a, b]: [A, B]): void {} + `, + + ` + type A = number; + type B = string; + const f = ([a, b]: [A, B]): void => {}; + `, + + ` + type A = number; + function f(a: A): void {} + `, + + ` + type A = number; + const f = (a: A): void => {}; + `, + + ` + interface A { + a: number; + } + + function f(a: A): A { + return a; + } + `, + + ` + interface A { + a: number; + } + + const f = (a: A): A => a; + `, + + ` + export type A = number; + export function f(a: A): void {} + `, + + ` + export type A = number; + export const f = (a: A): void => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(a: A | B): void {} + `, + + ` + export type A = number; + export type B = string; + export const f = (a: A | B): void => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(a: A & B): void {} + `, + + ` + export type A = number; + export type B = string; + export const f = (a: A & B): void => {}; + `, + + ` + export type A = number; + export function f(...args: A[]): void {} + `, + + ` + export type A = number; + export const f = (...args: A[]): void => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(args: { a: A, b: B, c: number }): void {} + `, + + ` + export type A = number; + export type B = string; + export const f = (args: { a: A, b: B, c: number }): void => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(args: [A, B]): void {} + `, + + ` + export type A = number; + export type B = string; + export const f = (args: [A, B]): void => {}; + `, + + ` + export type A = number; + export function f(a: A = 1): void {} + `, + + ` + export type A = number; + export const f = (a: A = 1): void => {}; + `, + + ` + export type A = number; + export function f(): A {} + `, + + ` + export type A = number; + export const f = (): A => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(): A | B {} + `, + + ` + export type A = number; + export type B = string; + export const f = (): A | B => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(): A & B {} + `, + + ` + export type A = number; + export type B = string; + export const f = (): A & B => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(): [A, B] {} + `, + + ` + export type A = number; + export type B = string; + export const f = (): [A, B] => {}; + `, + + ` + export type A = number; + export type B = string; + export function f(): { a: A, b: B } {} + `, + + ` + export type A = number; + export type B = string; + export const f = (): { a: A, b: B } => {}; + `, + ], + + invalid: [ + { + code: ` + type Arg = number; + + export function f(a: Arg): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (a: Arg): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export function f(a: Arg, b: Arg): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (a: Arg, b: Arg): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1, b: Arg2): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 43, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1, b: Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 43, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + + interface Arg2 { + a: string; + } + + export function f(a: Arg1, b: Arg2): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 8, + column: 39, + endColumn: 43, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + + interface Arg2 { + a: string; + } + + export const f = (a: Arg1, b: Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 8, + column: 39, + endColumn: 43, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1 | Arg2): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1 | Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: Arg1 & Arg2): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = (a: Arg1 & Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f([a, b]: [Arg1, Arg2, number]): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 36, + endColumn: 40, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 42, + endColumn: 46, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + type Arg3 = boolean; + + export function f([a, b]: [Arg1, Arg2, number], c: Arg3): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 36, + endColumn: 40, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 42, + endColumn: 46, + data: { + name: 'Arg2', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 60, + endColumn: 64, + data: { + name: 'Arg3', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = ([a, b]: [Arg1, Arg2, number]): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 36, + endColumn: 40, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 42, + endColumn: 46, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f({ a, b }: { a: Arg1; b: Arg2; c: number }): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 42, + endColumn: 46, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 51, + endColumn: 55, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f = ({ a, b }: { a: Arg1; b: Arg2; c: number }): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 42, + endColumn: 46, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 51, + endColumn: 55, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export function f(...args: Arg[]): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 36, + endColumn: 39, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (...args: Arg[]): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 36, + endColumn: 39, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export function f(a: Arg = 1): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (a: Arg = 1): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Arg', + }, + }, + ], + }, + + // TODO: Find a resaonable way to handle this case + { + code: ` + type Arg = number; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 37, + endColumn: 39, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export const f = (a: T): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 37, + endColumn: 39, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Ret = string; + + export function f(): Ret {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Ret', + }, + }, + ], + }, + + { + code: ` + type Ret = string; + + export const f = (): Ret => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 30, + endColumn: 33, + data: { + name: 'Ret', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): Ret1 | Ret2 {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): Ret1 | Ret2 => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): Ret1 & Ret2 {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): Ret1 & Ret2 => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 30, + endColumn: 34, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): [Ret1, Ret2, number, Ret1] {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 35, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): [Ret1, Ret2, number, Ret1] => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 35, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): { a: Ret1; b: Ret2; c: number; d: Ret1 } {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 35, + endColumn: 39, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export const f = (): { a: Ret1; b: Ret2; c: number; d: Ret1 } => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 35, + endColumn: 39, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export declare function f(a: Arg): void; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 38, + endColumn: 41, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export declare function f(a: Arg): Arg; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 38, + endColumn: 41, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export declare function f(a: Arg1): true; + export declare function f(a: Arg2): false; + export declare function f(a: Arg1 | Arg2): boolean; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 38, + endColumn: 42, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 38, + endColumn: 42, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export const f1 = (a: Arg1): void => {}, + f2 = (a: Arg2): void => {}; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 35, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 20, + endColumn: 24, + data: { + name: 'Arg2', + }, + }, + ], + }, + ], +}); From 7778868a552bfa6a692c6f2a00d6440a957853ec Mon Sep 17 00:00:00 2001 From: StyleShit Date: Mon, 12 Feb 2024 22:53:35 +0200 Subject: [PATCH 02/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index a972eb707461..19df3d44c79a 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -57,7 +57,7 @@ export default createRule<[], MessageIds>({ const name = getTypeName(paramTypeNode); if (!name) { - // TODO: Report on the whole function? + // TODO: Report on the whole function? Is this case even possible? return; } @@ -93,10 +93,11 @@ export default createRule<[], MessageIds>({ return; } - getReturnTypesNodes(returnTypeNode).forEach(returnTypeNode => { + getReturnTypeTypesNodes(returnTypeNode).forEach(returnTypeNode => { const name = getTypeName(returnTypeNode); if (!name) { + // TODO: Report on the whole function? Is this case even possi return; } @@ -198,7 +199,7 @@ export default createRule<[], MessageIds>({ return []; } - function getReturnTypesNodes( + function getReturnTypeTypesNodes( typeAnnotation: TSESTree.TSTypeAnnotation, ): TSESTree.TSTypeReference[] { // Single type From 12fce5b71b7de1cdab294cc48647b399b4ff7464 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Mon, 12 Feb 2024 22:55:53 +0200 Subject: [PATCH 03/86] wip --- .../src/rules/require-types-exports.ts | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 19df3d44c79a..78eb9e1eedca 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -24,6 +24,25 @@ export default createRule<[], MessageIds>({ const exportedTypes = new Set(); const reported = new Set(); + function collectExportedTypes(program: TSESTree.Program): void { + program.body.forEach(statement => { + if (statement.type !== AST_NODE_TYPES.ExportNamedDeclaration) { + return; + } + + const { declaration } = statement; + + if ( + declaration?.type === AST_NODE_TYPES.TSTypeAliasDeclaration || + declaration?.type === AST_NODE_TYPES.TSInterfaceDeclaration + ) { + exportedTypes.add(declaration.id.name); + + return; + } + }); + } + function visitExportedFunctionDeclaration( node: TSESTree.ExportNamedDeclaration & { declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; @@ -246,25 +265,6 @@ export default createRule<[], MessageIds>({ return []; } - function collectExportedTypes(node: TSESTree.Program): void { - node.body.forEach(statement => { - if (statement.type !== AST_NODE_TYPES.ExportNamedDeclaration) { - return; - } - - const { declaration } = statement; - - if ( - declaration?.type === AST_NODE_TYPES.TSTypeAliasDeclaration || - declaration?.type === AST_NODE_TYPES.TSInterfaceDeclaration - ) { - exportedTypes.add(declaration.id.name); - - return; - } - }); - } - function getTypeName(typeReference: TSESTree.TSTypeReference): string { if (typeReference.typeName.type === AST_NODE_TYPES.Identifier) { return typeReference.typeName.name; From d62f86c020f208567c6417b02bd941cabcce5e74 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 18:55:43 +0200 Subject: [PATCH 04/86] lint --- packages/eslint-plugin/src/rules/require-types-exports.ts | 2 +- .../tests/rules/require-types-exports.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 78eb9e1eedca..0c0d6ec7026a 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -116,7 +116,7 @@ export default createRule<[], MessageIds>({ const name = getTypeName(returnTypeNode); if (!name) { - // TODO: Report on the whole function? Is this case even possi + // TODO: Report on the whole function? Is this case even possible? return; } diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index f8d0437e27f7..51f1bcfc82b6 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -170,13 +170,13 @@ ruleTester.run('require-types-exports', rule, { ` export type A = number; export type B = string; - export function f(args: { a: A, b: B, c: number }): void {} + 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 const f = (args: { a: A; b: B; c: number }): void => {}; `, ` @@ -250,13 +250,13 @@ ruleTester.run('require-types-exports', rule, { ` export type A = number; export type B = string; - export function f(): { a: A, b: B } {} + export function f(): { a: A; b: B } {} `, ` export type A = number; export type B = string; - export const f = (): { a: A, b: B } => {}; + export const f = (): { a: A; b: B } => {}; `, ], From 0ebebd2919345c86b2244b31069b49583ffb7933 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 19:11:33 +0200 Subject: [PATCH 05/86] wip --- packages/eslint-plugin/src/configs/all.ts | 1 + packages/eslint-plugin/src/configs/disable-type-checked.ts | 1 + packages/eslint-plugin/src/configs/strict-type-checked.ts | 1 + packages/eslint-plugin/src/rules/index.ts | 2 ++ packages/eslint-plugin/src/rules/require-types-exports.ts | 1 + 5 files changed, 6 insertions(+) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index b1890165c7ad..f29feca16e3f 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -142,6 +142,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 09a5c07fd3e7..a0641c0c49fb 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -55,6 +55,7 @@ export = { '@typescript-eslint/promise-function-async': 'off', '@typescript-eslint/require-array-sort-compare': 'off', '@typescript-eslint/require-await': 'off', + '@typescript-eslint/require-types-exports': 'off', '@typescript-eslint/restrict-plus-operands': 'off', '@typescript-eslint/restrict-template-expressions': 'off', '@typescript-eslint/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 5666c64035da..7bcf3f5920ca 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -71,6 +71,7 @@ export = { '@typescript-eslint/prefer-ts-expect-error': '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', '@typescript-eslint/triple-slash-reference': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index e497019debec..5dc2b88b71c0 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -125,6 +125,7 @@ import promiseFunctionAsync from './promise-function-async'; import quotes from './quotes'; import requireArraySortCompare from './require-array-sort-compare'; import requireAwait from './require-await'; +import requireTypesExports from './require-types-exports'; import restrictPlusOperands from './restrict-plus-operands'; import restrictTemplateExpressions from './restrict-template-expressions'; import returnAwait from './return-await'; @@ -267,6 +268,7 @@ export default { quotes: quotes, 'require-array-sort-compare': requireArraySortCompare, 'require-await': requireAwait, + 'require-types-exports': requireTypesExports, 'restrict-plus-operands': restrictPlusOperands, 'restrict-template-expressions': restrictTemplateExpressions, 'return-await': returnAwait, diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 0c0d6ec7026a..32f178dd95a7 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -11,6 +11,7 @@ export default createRule<[], MessageIds>({ type: 'suggestion', docs: { recommended: 'strict', + requiresTypeChecking: true, description: 'Require exporting types that are used in exported functions declarations', }, From bfee79153753777b133c5b0fe583783b60016fe4 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 19:17:25 +0200 Subject: [PATCH 06/86] spelling... --- .../eslint-plugin/tests/rules/require-types-exports.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 51f1bcfc82b6..d78a12b004d9 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -806,7 +806,7 @@ ruleTester.run('require-types-exports', rule, { ], }, - // TODO: Find a resaonable way to handle this case + // TODO: Find a reasonable way to handle this case { code: ` type Arg = number; From b309b51e1ae3246441e31e77724a5046f850f2d7 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 20:24:39 +0200 Subject: [PATCH 07/86] wip --- .../docs/rules/require-types-exports.md | 75 ++++++++++ .../src/rules/require-types-exports.ts | 59 +++++++- .../tests/rules/require-types-exports.test.ts | 129 ++++++++++++++++++ 3 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/require-types-exports.md diff --git a/packages/eslint-plugin/docs/rules/require-types-exports.md b/packages/eslint-plugin/docs/rules/require-types-exports.md new file mode 100644 index 000000000000..767050deeba0 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/require-types-exports.md @@ -0,0 +1,75 @@ +--- +description: 'Require exporting types that are used in exported functions declarations.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/require-types-exports** for documentation. + +When exporting functions from a module, it is recommended to export also all the +types that are used in the function declarations. This is useful for consumers of +the module, as it allows them to use the types in their own code without having to +use things like [`Parameters`](https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype) +or [`ReturnType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype) to extract the types from the function. + +## Examples + + + +### ❌ Incorrect + +```ts +type Arg = string; +type Result = number; + +export function strLength(arg: Arg): Result { + return arg.length; +} + +interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; + +enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +``` + +### ✅ Correct + +```ts +export type Arg = string; +export type Result = number; + +export function strLength(arg: Arg): Result { + return arg.length; +} + +export interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; + +export enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +``` + + + +## When Not To Use It + +When you don't want to enforce exporting types that are used in exported functions declarations. diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 32f178dd95a7..9a65af9ba399 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,7 +1,8 @@ -import type { TSESTree } from '@typescript-eslint/utils'; +import { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { createRule, getParserServices } from '../util'; +import { DefinitionType } from '@typescript-eslint/scope-manager'; type MessageIds = 'requireTypeExport'; @@ -45,7 +46,10 @@ export default createRule<[], MessageIds>({ } function visitExportedFunctionDeclaration( - node: TSESTree.ExportNamedDeclaration & { + node: ( + | TSESTree.ExportNamedDeclaration + | TSESTree.DefaultExportDeclarations + ) & { declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; }, ): void { @@ -66,11 +70,46 @@ export default createRule<[], MessageIds>({ }); } + function visitDefaultExportedArrowFunction( + node: TSESTree.ExportDefaultDeclaration & { + declaration: TSESTree.ArrowFunctionExpression; + }, + ): void { + checkFunctionParamsTypes(node.declaration); + checkFunctionReturnType(node.declaration); + } + + function visitDefaultExportedIdentifier( + node: TSESTree.DefaultExportDeclarations & { + declaration: TSESTree.Identifier; + }, + ) { + const scope = context.sourceCode.getScope(node); + const variable = scope.set.get(node.declaration.name); + + if (!variable) { + return; + } + + for (const definition of variable.defs) { + if ( + definition.type === DefinitionType.Variable && + (definition.node.init?.type === + AST_NODE_TYPES.ArrowFunctionExpression || + definition.node.init?.type === AST_NODE_TYPES.FunctionExpression) + ) { + checkFunctionParamsTypes(definition.node.init); + checkFunctionReturnType(definition.node.init); + } + } + } + function checkFunctionParamsTypes( node: | TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction - | TSESTree.ArrowFunctionExpression, + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionExpression, ): void { node.params.forEach(param => { getParamTypesNodes(param).forEach(paramTypeNode => { @@ -105,7 +144,8 @@ export default createRule<[], MessageIds>({ node: | TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction - | TSESTree.ArrowFunctionExpression, + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionExpression, ): void { const returnTypeNode = node.returnType; @@ -285,6 +325,15 @@ export default createRule<[], MessageIds>({ 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': visitExportedVariableDeclaration, + + 'ExportDefaultDeclaration[declaration.type="FunctionDeclaration"]': + visitExportedFunctionDeclaration, + + 'ExportDefaultDeclaration[declaration.type="ArrowFunctionExpression"]': + visitDefaultExportedArrowFunction, + + 'ExportDefaultDeclaration[declaration.type="Identifier"]': + visitDefaultExportedIdentifier, }; }, }); diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index d78a12b004d9..5832d5739c42 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -42,6 +42,9 @@ ruleTester.run('require-types-exports', rule, { 'export function f(...args: unknown[]): void {}', 'export const f = (...args: unknown[]): void => {};', + 'export default function f(): void {}', + 'export default (): void => {};', + ` type A = number; function f(a: A): A { @@ -299,6 +302,44 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type Arg = number; + + export default function(a: Arg): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 36, + endColumn: 39, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + export default (a: Arg): void => {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 28, + endColumn: 31, + data: { + name: 'Arg', + }, + }, + ], + }, + { code: ` type Arg = number; @@ -806,6 +847,52 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + export function f(a: Fruit): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 30, + endColumn: 35, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + export const f = (a: Fruit): void => {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 30, + endColumn: 35, + data: { + name: 'Fruit', + }, + }, + ], + }, + // TODO: Find a reasonable way to handle this case { code: ` @@ -1115,6 +1202,48 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type Arg = number; + + const a = (a: Arg): void => {}; + + export default a; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 23, + endColumn: 26, + data: { + name: 'Arg', + }, + }, + ], + }, + + { + code: ` + type Arg = number; + + const a = function (a: Arg): void {}; + + export default a; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 32, + endColumn: 35, + data: { + name: 'Arg', + }, + }, + ], + }, + { code: ` type Arg = number; From 6aa6446ed501c801bce0e5af7d161f8a750279c8 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 20:29:52 +0200 Subject: [PATCH 08/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 9a65af9ba399..3614f728828c 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,7 +1,7 @@ import { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule, getParserServices } from '../util'; +import { createRule } from '../util'; import { DefinitionType } from '@typescript-eslint/scope-manager'; type MessageIds = 'requireTypeExport'; From 892c368fdb3787ec561660a8fd05448be1e75620 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 21:08:05 +0200 Subject: [PATCH 09/86] wip --- .../src/rules/require-types-exports.ts | 146 +++++++---- .../tests/rules/require-types-exports.test.ts | 245 +++++++++++++++++- .../require-types-exports.shot | 14 + 3 files changed, 347 insertions(+), 58 deletions(-) create mode 100644 packages/eslint-plugin/tests/schema-snapshots/require-types-exports.shot diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 3614f728828c..ffeafecf9aef 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -6,6 +6,12 @@ import { DefinitionType } from '@typescript-eslint/scope-manager'; type MessageIds = 'requireTypeExport'; +type FunctionNode = + | TSESTree.FunctionDeclaration + | TSESTree.TSDeclareFunction + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionExpression; + export default createRule<[], MessageIds>({ name: 'require-types-exports', meta: { @@ -104,16 +110,53 @@ export default createRule<[], MessageIds>({ } } - function checkFunctionParamsTypes( - node: - | TSESTree.FunctionDeclaration - | TSESTree.TSDeclareFunction - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionExpression, - ): void { + function checkFunctionParamsTypes(node: FunctionNode): void { node.params.forEach(param => { - getParamTypesNodes(param).forEach(paramTypeNode => { - const name = getTypeName(paramTypeNode); + getParamTypesNodes(param) + .flatMap(paramTypeNode => { + return convertGenericTypeToTypeReference(node, paramTypeNode); + }) + .forEach(paramTypeNode => { + const name = getTypeName(paramTypeNode); + + if (!name) { + // TODO: Report on the whole function? Is this case even possible? + return; + } + + const isExported = exportedTypes.has(name); + const isReported = reported.has(name); + + if (isExported || isReported) { + return; + } + + context.report({ + node: paramTypeNode, + messageId: 'requireTypeExport', + data: { + name, + }, + }); + + reported.add(name); + }); + }); + } + + function checkFunctionReturnType(node: FunctionNode): void { + const returnTypeNode = node.returnType; + + if (!returnTypeNode) { + return; + } + + getReturnTypeTypesNodes(returnTypeNode) + .flatMap(paramTypeNode => { + return convertGenericTypeToTypeReference(node, paramTypeNode); + }) + .forEach(returnTypeNode => { + const name = getTypeName(returnTypeNode); if (!name) { // TODO: Report on the whole function? Is this case even possible? @@ -128,7 +171,7 @@ export default createRule<[], MessageIds>({ } context.report({ - node: paramTypeNode, + node: returnTypeNode, messageId: 'requireTypeExport', data: { name, @@ -137,47 +180,6 @@ export default createRule<[], MessageIds>({ reported.add(name); }); - }); - } - - function checkFunctionReturnType( - node: - | TSESTree.FunctionDeclaration - | TSESTree.TSDeclareFunction - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionExpression, - ): void { - const returnTypeNode = node.returnType; - - if (!returnTypeNode) { - return; - } - - getReturnTypeTypesNodes(returnTypeNode).forEach(returnTypeNode => { - const name = getTypeName(returnTypeNode); - - if (!name) { - // TODO: Report on the whole function? Is this case even possible? - return; - } - - const isExported = exportedTypes.has(name); - const isReported = reported.has(name); - - if (isExported || isReported) { - return; - } - - context.report({ - node: returnTypeNode, - messageId: 'requireTypeExport', - data: { - name, - }, - }); - - reported.add(name); - }); } function getParamTypesNodes( @@ -306,6 +308,48 @@ export default createRule<[], MessageIds>({ return []; } + function convertGenericTypeToTypeReference( + functionNode: FunctionNode, + typeNode: TSESTree.TSTypeReference, + ): TSESTree.TSTypeReference | TSESTree.TSTypeReference[] { + const typeName = getTypeName(typeNode); + + if (!typeName) { + return typeNode; + } + + const scope = context.sourceCode.getScope(functionNode); + const variable = scope.set.get(typeName); + + if (!variable || !variable.isTypeVariable) { + return typeNode; + } + + for (const definition of variable.defs) { + if ( + definition.type === DefinitionType.Type && + definition.node.type === AST_NODE_TYPES.TSTypeParameter && + definition.node.constraint + ) { + switch (definition.node.constraint.type) { + case AST_NODE_TYPES.TSTypeReference: + return definition.node.constraint; + + case AST_NODE_TYPES.TSUnionType: + case AST_NODE_TYPES.TSIntersectionType: + return definition.node.constraint.types.filter( + type => type.type === AST_NODE_TYPES.TSTypeReference, + ) as TSESTree.TSTypeReference[]; + + default: + continue; + } + } + } + + return typeNode; + } + function getTypeName(typeReference: TSESTree.TSTypeReference): string { if (typeReference.typeName.type === AST_NODE_TYPES.Identifier) { return typeReference.typeName.name; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 5832d5739c42..6bdc65c1dda1 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -893,7 +893,6 @@ ruleTester.run('require-types-exports', rule, { ], }, - // TODO: Find a reasonable way to handle this case { code: ` type Arg = number; @@ -905,7 +904,7 @@ ruleTester.run('require-types-exports', rule, { messageId: 'requireTypeExport', line: 4, column: 37, - endColumn: 39, + endColumn: 40, data: { name: 'Arg', }, @@ -915,18 +914,115 @@ ruleTester.run('require-types-exports', rule, { { code: ` - type Arg = number; + type Arg1 = number; + type Arg2 = string; - export const f = (a: T): void => {}; + export function f(a: T): void {} `, errors: [ { messageId: 'requireTypeExport', - line: 4, + line: 5, column: 37, - endColumn: 39, + endColumn: 41, data: { - name: 'Arg', + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Arg2', + }, + }, + ], + }, + + { + code: ` + type Arg1 = number; + type Arg2 = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Arg2', }, }, ], @@ -1202,6 +1298,141 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type Ret = string; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 37, + endColumn: 40, + data: { + name: 'Ret', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + + { + code: ` + type Ret1 = string; + type Ret2 = number; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 41, + data: { + name: 'Ret1', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 44, + endColumn: 48, + data: { + name: 'Ret2', + }, + }, + ], + }, + { code: ` type Arg = number; 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 = [];" +`; From 0e8e58fc52567ec571b014598a345d6814e31e71 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 21:15:26 +0200 Subject: [PATCH 10/86] tuple generic --- .../src/rules/require-types-exports.ts | 9 +++++ .../tests/rules/require-types-exports.test.ts | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index ffeafecf9aef..b4b8eb5f3b66 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -332,15 +332,24 @@ export default createRule<[], MessageIds>({ definition.node.constraint ) { switch (definition.node.constraint.type) { + // T extends SomeType case AST_NODE_TYPES.TSTypeReference: return definition.node.constraint; + // T extends SomeType | AnotherType + // T extends SomeType & AnotherType case AST_NODE_TYPES.TSUnionType: case AST_NODE_TYPES.TSIntersectionType: return definition.node.constraint.types.filter( type => type.type === AST_NODE_TYPES.TSTypeReference, ) as TSESTree.TSTypeReference[]; + // T extends [SomeType, AnotherType] + case AST_NODE_TYPES.TSTupleType: + return definition.node.constraint.elementTypes.filter( + type => type.type === AST_NODE_TYPES.TSTypeReference, + ) as TSESTree.TSTypeReference[]; + default: continue; } diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 6bdc65c1dda1..a90aa473964b 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -1028,6 +1028,25 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type Arg = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 38, + endColumn: 41, + data: { + name: 'Arg', + }, + }, + ], + }, + { code: ` type Ret = string; @@ -1433,6 +1452,25 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type Ret = string; + + export function f(): T {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 38, + endColumn: 41, + data: { + name: 'Ret', + }, + }, + ], + }, + { code: ` type Arg = number; From f4018a8f43603800f3113a4fceacd3c83c11f136 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 21:37:17 +0200 Subject: [PATCH 11/86] wip --- .../src/rules/require-types-exports.ts | 16 ++++++++++++++ .../tests/rules/require-types-exports.test.ts | 21 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index b4b8eb5f3b66..4e208e813d7b 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -350,6 +350,22 @@ export default createRule<[], MessageIds>({ type => type.type === AST_NODE_TYPES.TSTypeReference, ) as TSESTree.TSTypeReference[]; + // T extends { some: SomeType, another: AnotherType } + case AST_NODE_TYPES.TSTypeLiteral: + return definition.node.constraint.members.reduce< + TSESTree.TSTypeReference[] + >((acc, member) => { + if ( + member.type === AST_NODE_TYPES.TSPropertySignature && + member.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSTypeReference + ) { + acc.push(member.typeAnnotation.typeAnnotation); + } + + return acc; + }, []); + default: continue; } diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index a90aa473964b..0a8833f44c07 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -1004,7 +1004,7 @@ ruleTester.run('require-types-exports', rule, { type Arg1 = number; type Arg2 = string; - export function f(a: T): void {} + export const f = (a: T): void => {} `, errors: [ { @@ -1047,6 +1047,25 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type Arg = string; + + export function f(a: T): void {} + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 42, + endColumn: 45, + data: { + name: 'Arg', + }, + }, + ], + }, + { code: ` type Ret = string; From 89a8344b63b03d0dd2689266637af5f419e27d58 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 13 Feb 2024 22:11:48 +0200 Subject: [PATCH 12/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 4e208e813d7b..281b204556cf 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -321,7 +321,7 @@ export default createRule<[], MessageIds>({ const scope = context.sourceCode.getScope(functionNode); const variable = scope.set.get(typeName); - if (!variable || !variable.isTypeVariable) { + if (!variable?.isTypeVariable) { return typeNode; } From b2138e36213a51de1f750390ef4394a2364d3cfd Mon Sep 17 00:00:00 2001 From: StyleShit Date: Thu, 15 Feb 2024 16:01:13 +0200 Subject: [PATCH 13/86] wip --- .../src/rules/require-types-exports.ts | 51 +++++++++---------- .../tests/rules/require-types-exports.test.ts | 36 ++++++++++--- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 281b204556cf..0f55054e3b06 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,8 +1,8 @@ -import { TSESTree } from '@typescript-eslint/utils'; +import { DefinitionType } from '@typescript-eslint/scope-manager'; +import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -import { DefinitionType } from '@typescript-eslint/scope-manager'; type MessageIds = 'requireTypeExport'; @@ -29,26 +29,17 @@ export default createRule<[], MessageIds>({ }, defaultOptions: [], create(context) { - const exportedTypes = new Set(); - const reported = new Set(); - - function collectExportedTypes(program: TSESTree.Program): void { - program.body.forEach(statement => { - if (statement.type !== AST_NODE_TYPES.ExportNamedDeclaration) { - return; - } + const externalizedTypes = new Set(); + const reportedTypes = new Set(); - const { declaration } = statement; - - if ( - declaration?.type === AST_NODE_TYPES.TSTypeAliasDeclaration || - declaration?.type === AST_NODE_TYPES.TSInterfaceDeclaration - ) { - exportedTypes.add(declaration.id.name); + function collectImportedTypes(node: TSESTree.ImportSpecifier): void { + externalizedTypes.add(node.local.name); + } - return; - } - }); + function collectExportedTypes( + node: TSESTree.TSTypeAliasDeclaration | TSESTree.TSInterfaceDeclaration, + ): void { + externalizedTypes.add(node.id.name); } function visitExportedFunctionDeclaration( @@ -89,7 +80,7 @@ export default createRule<[], MessageIds>({ node: TSESTree.DefaultExportDeclarations & { declaration: TSESTree.Identifier; }, - ) { + ): void { const scope = context.sourceCode.getScope(node); const variable = scope.set.get(node.declaration.name); @@ -124,8 +115,8 @@ export default createRule<[], MessageIds>({ return; } - const isExported = exportedTypes.has(name); - const isReported = reported.has(name); + const isExported = externalizedTypes.has(name); + const isReported = reportedTypes.has(name); if (isExported || isReported) { return; @@ -139,7 +130,7 @@ export default createRule<[], MessageIds>({ }, }); - reported.add(name); + reportedTypes.add(name); }); }); } @@ -163,8 +154,8 @@ export default createRule<[], MessageIds>({ return; } - const isExported = exportedTypes.has(name); - const isReported = reported.has(name); + const isExported = externalizedTypes.has(name); + const isReported = reportedTypes.has(name); if (isExported || isReported) { return; @@ -178,7 +169,7 @@ export default createRule<[], MessageIds>({ }, }); - reported.add(name); + reportedTypes.add(name); }); } @@ -384,7 +375,11 @@ export default createRule<[], MessageIds>({ } return { - Program: collectExportedTypes, + 'ImportDeclaration[importKind="type"] ImportSpecifier, ImportSpecifier[importKind="type"]': + collectImportedTypes, + + 'ExportNamedDeclaration TSTypeAliasDeclaration, ExportNamedDeclaration TSInterfaceDeclaration': + collectExportedTypes, 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': visitExportedFunctionDeclaration, diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 0a8833f44c07..c83b3b893413 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -261,6 +261,30 @@ ruleTester.run('require-types-exports', rule, { export type B = string; export const f = (): { a: A; b: B } => {}; `, + + ` + import { testFunction, type Arg } from './module'; + + 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 {} + `, ], invalid: [ @@ -306,14 +330,14 @@ ruleTester.run('require-types-exports', rule, { code: ` type Arg = number; - export default function(a: Arg): void {} + export default function (a: Arg): void {} `, errors: [ { messageId: 'requireTypeExport', line: 4, - column: 36, - endColumn: 39, + column: 37, + endColumn: 40, data: { name: 'Arg', }, @@ -325,7 +349,7 @@ ruleTester.run('require-types-exports', rule, { code: ` type Arg = number; - export default (a: Arg): void => {} + export default (a: Arg): void => {}; `, errors: [ { @@ -878,7 +902,7 @@ ruleTester.run('require-types-exports', rule, { Cherry, } - export const f = (a: Fruit): void => {} + export const f = (a: Fruit): void => {}; `, errors: [ { @@ -1004,7 +1028,7 @@ ruleTester.run('require-types-exports', rule, { type Arg1 = number; type Arg2 = string; - export const f = (a: T): void => {} + export const f = (a: T): void => {}; `, errors: [ { From 1161db04d32ea6039c4646b096d3817dbe59de02 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Fri, 16 Feb 2024 14:15:10 +0200 Subject: [PATCH 14/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 0f55054e3b06..681ee9d16757 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -115,10 +115,10 @@ export default createRule<[], MessageIds>({ return; } - const isExported = externalizedTypes.has(name); + const isExternalized = externalizedTypes.has(name); const isReported = reportedTypes.has(name); - if (isExported || isReported) { + if (isExternalized || isReported) { return; } @@ -154,10 +154,10 @@ export default createRule<[], MessageIds>({ return; } - const isExported = externalizedTypes.has(name); + const isExternalized = externalizedTypes.has(name); const isReported = reportedTypes.has(name); - if (isExported || isReported) { + if (isExternalized || isReported) { return; } From d9875b32873e8cbaf87749fc35a37c1a915d99de Mon Sep 17 00:00:00 2001 From: StyleShit Date: Fri, 16 Feb 2024 14:22:47 +0200 Subject: [PATCH 15/86] refactor --- .../src/rules/require-types-exports.ts | 98 ++++++++++--------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 681ee9d16757..a71f9c09ca0c 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -102,55 +102,16 @@ export default createRule<[], MessageIds>({ } function checkFunctionParamsTypes(node: FunctionNode): void { - node.params.forEach(param => { - getParamTypesNodes(param) - .flatMap(paramTypeNode => { - return convertGenericTypeToTypeReference(node, paramTypeNode); - }) - .forEach(paramTypeNode => { - const name = getTypeName(paramTypeNode); - - if (!name) { - // TODO: Report on the whole function? Is this case even possible? - return; - } - - const isExternalized = externalizedTypes.has(name); - const isReported = reportedTypes.has(name); - - if (isExternalized || isReported) { - return; - } - - context.report({ - node: paramTypeNode, - messageId: 'requireTypeExport', - data: { - name, - }, - }); - - reportedTypes.add(name); - }); - }); - } - - function checkFunctionReturnType(node: FunctionNode): void { - const returnTypeNode = node.returnType; - - if (!returnTypeNode) { - return; - } + for (const param of node.params) { + const typeNodes = getParamTypesNodes(param).flatMap(typeNode => { + return convertGenericTypeToTypeReferences(node, typeNode); + }); - getReturnTypeTypesNodes(returnTypeNode) - .flatMap(paramTypeNode => { - return convertGenericTypeToTypeReference(node, paramTypeNode); - }) - .forEach(returnTypeNode => { - const name = getTypeName(returnTypeNode); + for (const typeNode of typeNodes) { + const name = getTypeName(typeNode); if (!name) { - // TODO: Report on the whole function? Is this case even possible? + // TODO: Report the whole function? Is this case even possible? return; } @@ -162,7 +123,7 @@ export default createRule<[], MessageIds>({ } context.report({ - node: returnTypeNode, + node: typeNode, messageId: 'requireTypeExport', data: { name, @@ -170,7 +131,48 @@ export default createRule<[], MessageIds>({ }); reportedTypes.add(name); + } + } + } + + function checkFunctionReturnType(node: FunctionNode): void { + const { returnType } = node; + + if (!returnType) { + return; + } + + const typeNodes = getReturnTypeTypesNodes(returnType).flatMap( + typeNode => { + return convertGenericTypeToTypeReferences(node, typeNode); + }, + ); + + for (const typeNode of typeNodes) { + const name = getTypeName(typeNode); + + if (!name) { + // TODO: Report the whole function? Is this case even possible? + return; + } + + const isExternalized = externalizedTypes.has(name); + const isReported = reportedTypes.has(name); + + if (isExternalized || isReported) { + return; + } + + context.report({ + node: typeNode, + messageId: 'requireTypeExport', + data: { + name, + }, }); + + reportedTypes.add(name); + } } function getParamTypesNodes( @@ -299,7 +301,7 @@ export default createRule<[], MessageIds>({ return []; } - function convertGenericTypeToTypeReference( + function convertGenericTypeToTypeReferences( functionNode: FunctionNode, typeNode: TSESTree.TSTypeReference, ): TSESTree.TSTypeReference | TSESTree.TSTypeReference[] { From 6338202219bc9f38172b33a45ca8624e9dcffaa6 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Fri, 16 Feb 2024 14:42:09 +0200 Subject: [PATCH 16/86] make it shorter & more readable --- .../src/rules/require-types-exports.ts | 180 ++++++++---------- 1 file changed, 79 insertions(+), 101 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index a71f9c09ca0c..66347fb95569 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -59,12 +59,12 @@ export default createRule<[], MessageIds>({ declaration: TSESTree.VariableDeclaration; }, ): void { - node.declaration.declarations.forEach(declaration => { + for (const declaration of node.declaration.declarations) { if (declaration.init?.type === AST_NODE_TYPES.ArrowFunctionExpression) { checkFunctionParamsTypes(declaration.init); checkFunctionReturnType(declaration.init); } - }); + } } function visitDefaultExportedArrowFunction( @@ -88,49 +88,26 @@ export default createRule<[], MessageIds>({ return; } - for (const definition of variable.defs) { + for (const def of variable.defs) { if ( - definition.type === DefinitionType.Variable && - (definition.node.init?.type === - AST_NODE_TYPES.ArrowFunctionExpression || - definition.node.init?.type === AST_NODE_TYPES.FunctionExpression) + def.type === DefinitionType.Variable && + (def.node.init?.type === AST_NODE_TYPES.ArrowFunctionExpression || + def.node.init?.type === AST_NODE_TYPES.FunctionExpression) ) { - checkFunctionParamsTypes(definition.node.init); - checkFunctionReturnType(definition.node.init); + checkFunctionParamsTypes(def.node.init); + checkFunctionReturnType(def.node.init); } } } function checkFunctionParamsTypes(node: FunctionNode): void { for (const param of node.params) { - const typeNodes = getParamTypesNodes(param).flatMap(typeNode => { + const typeNodes = getParamTypeNodes(param).flatMap(typeNode => { return convertGenericTypeToTypeReferences(node, typeNode); }); for (const typeNode of typeNodes) { - const name = getTypeName(typeNode); - - if (!name) { - // TODO: Report the whole function? Is this case even possible? - return; - } - - const isExternalized = externalizedTypes.has(name); - const isReported = reportedTypes.has(name); - - if (isExternalized || isReported) { - return; - } - - context.report({ - node: typeNode, - messageId: 'requireTypeExport', - data: { - name, - }, - }); - - reportedTypes.add(name); + checkTypeNode(typeNode); } } } @@ -142,40 +119,42 @@ export default createRule<[], MessageIds>({ return; } - const typeNodes = getReturnTypeTypesNodes(returnType).flatMap( - typeNode => { - return convertGenericTypeToTypeReferences(node, typeNode); - }, - ); + const typeNodes = getReturnTypeTypeNodes(returnType).flatMap(typeNode => { + return convertGenericTypeToTypeReferences(node, typeNode); + }); for (const typeNode of typeNodes) { - const name = getTypeName(typeNode); - - if (!name) { - // TODO: Report the whole function? Is this case even possible? - return; - } + checkTypeNode(typeNode); + } + } - const isExternalized = externalizedTypes.has(name); - const isReported = reportedTypes.has(name); + function checkTypeNode(node: TSESTree.TSTypeReference): void { + const name = getTypeName(node); - if (isExternalized || isReported) { - return; - } + if (!name) { + // TODO: Report the whole function? Is this case even possible? + return; + } - context.report({ - node: typeNode, - messageId: 'requireTypeExport', - data: { - name, - }, - }); + const isExternalized = externalizedTypes.has(name); + const isReported = reportedTypes.has(name); - reportedTypes.add(name); + if (isExternalized || isReported) { + return; } + + context.report({ + node: node, + messageId: 'requireTypeExport', + data: { + name, + }, + }); + + reportedTypes.add(name); } - function getParamTypesNodes( + function getParamTypeNodes( param: TSESTree.Parameter, ): TSESTree.TSTypeReference[] { // Single type @@ -254,7 +233,7 @@ export default createRule<[], MessageIds>({ return []; } - function getReturnTypeTypesNodes( + function getReturnTypeTypeNodes( typeAnnotation: TSESTree.TSTypeAnnotation, ): TSESTree.TSTypeReference[] { // Single type @@ -318,50 +297,49 @@ export default createRule<[], MessageIds>({ return typeNode; } - for (const definition of variable.defs) { + for (const def of variable.defs) { if ( - definition.type === DefinitionType.Type && - definition.node.type === AST_NODE_TYPES.TSTypeParameter && - definition.node.constraint + def.type !== DefinitionType.Type || + def.node.type !== AST_NODE_TYPES.TSTypeParameter || + !def.node.constraint ) { - switch (definition.node.constraint.type) { - // T extends SomeType - case AST_NODE_TYPES.TSTypeReference: - return definition.node.constraint; - - // T extends SomeType | AnotherType - // T extends SomeType & AnotherType - case AST_NODE_TYPES.TSUnionType: - case AST_NODE_TYPES.TSIntersectionType: - return definition.node.constraint.types.filter( - type => type.type === AST_NODE_TYPES.TSTypeReference, - ) as TSESTree.TSTypeReference[]; - - // T extends [SomeType, AnotherType] - case AST_NODE_TYPES.TSTupleType: - return definition.node.constraint.elementTypes.filter( - type => type.type === AST_NODE_TYPES.TSTypeReference, - ) as TSESTree.TSTypeReference[]; - - // T extends { some: SomeType, another: AnotherType } - case AST_NODE_TYPES.TSTypeLiteral: - return definition.node.constraint.members.reduce< - TSESTree.TSTypeReference[] - >((acc, member) => { - if ( - member.type === AST_NODE_TYPES.TSPropertySignature && - member.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSTypeReference - ) { - acc.push(member.typeAnnotation.typeAnnotation); - } - - return acc; - }, []); - - default: - continue; - } + continue; + } + + switch (def.node.constraint.type) { + // T extends SomeType + case AST_NODE_TYPES.TSTypeReference: + return def.node.constraint; + + // T extends SomeType | AnotherType + // T extends SomeType & AnotherType + case AST_NODE_TYPES.TSUnionType: + case AST_NODE_TYPES.TSIntersectionType: + return def.node.constraint.types.filter( + type => type.type === AST_NODE_TYPES.TSTypeReference, + ) as TSESTree.TSTypeReference[]; + + // T extends [SomeType, AnotherType] + case AST_NODE_TYPES.TSTupleType: + return def.node.constraint.elementTypes.filter( + type => type.type === AST_NODE_TYPES.TSTypeReference, + ) as TSESTree.TSTypeReference[]; + + // T extends { some: SomeType, another: AnotherType } + case AST_NODE_TYPES.TSTypeLiteral: + return def.node.constraint.members.reduce< + TSESTree.TSTypeReference[] + >((acc, member) => { + if ( + member.type === AST_NODE_TYPES.TSPropertySignature && + member.typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSTypeReference + ) { + acc.push(member.typeAnnotation.typeAnnotation); + } + + return acc; + }, []); } } From 1812e37f6501844fc72fc1d4ae006300464a8e34 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 24 Apr 2024 12:21:57 +0300 Subject: [PATCH 17/86] fix nested types in functions --- .../src/rules/require-types-exports.ts | 240 +----------------- .../tests/rules/require-types-exports.test.ts | 48 ++++ 2 files changed, 62 insertions(+), 226 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 66347fb95569..b99ba0ea7102 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -50,8 +50,7 @@ export default createRule<[], MessageIds>({ declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; }, ): void { - checkFunctionParamsTypes(node.declaration); - checkFunctionReturnType(node.declaration); + checkFunctionTypes(node.declaration); } function visitExportedVariableDeclaration( @@ -61,8 +60,7 @@ export default createRule<[], MessageIds>({ ): void { for (const declaration of node.declaration.declarations) { if (declaration.init?.type === AST_NODE_TYPES.ArrowFunctionExpression) { - checkFunctionParamsTypes(declaration.init); - checkFunctionReturnType(declaration.init); + checkFunctionTypes(declaration.init); } } } @@ -72,8 +70,7 @@ export default createRule<[], MessageIds>({ declaration: TSESTree.ArrowFunctionExpression; }, ): void { - checkFunctionParamsTypes(node.declaration); - checkFunctionReturnType(node.declaration); + checkFunctionTypes(node.declaration); } function visitDefaultExportedIdentifier( @@ -94,38 +91,21 @@ export default createRule<[], MessageIds>({ (def.node.init?.type === AST_NODE_TYPES.ArrowFunctionExpression || def.node.init?.type === AST_NODE_TYPES.FunctionExpression) ) { - checkFunctionParamsTypes(def.node.init); - checkFunctionReturnType(def.node.init); + checkFunctionTypes(def.node.init); } } } - function checkFunctionParamsTypes(node: FunctionNode): void { - for (const param of node.params) { - const typeNodes = getParamTypeNodes(param).flatMap(typeNode => { - return convertGenericTypeToTypeReferences(node, typeNode); - }); - - for (const typeNode of typeNodes) { - checkTypeNode(typeNode); - } - } - } - - function checkFunctionReturnType(node: FunctionNode): void { - const { returnType } = node; - - if (!returnType) { - return; - } - - const typeNodes = getReturnTypeTypeNodes(returnType).flatMap(typeNode => { - return convertGenericTypeToTypeReferences(node, typeNode); - }); + function checkFunctionTypes(node: FunctionNode): void { + const scope = context.sourceCode.getScope(node); - for (const typeNode of typeNodes) { - checkTypeNode(typeNode); - } + scope.through + .map(ref => ref.identifier.parent) + .filter( + (node): node is TSESTree.TSTypeReference => + node.type === AST_NODE_TYPES.TSTypeReference, + ) + .forEach(checkTypeNode); } function checkTypeNode(node: TSESTree.TSTypeReference): void { @@ -154,198 +134,6 @@ export default createRule<[], MessageIds>({ reportedTypes.add(name); } - function getParamTypeNodes( - param: TSESTree.Parameter, - ): TSESTree.TSTypeReference[] { - // Single type - if ( - param.type === AST_NODE_TYPES.Identifier && - param.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSTypeReference - ) { - return [param.typeAnnotation.typeAnnotation]; - } - - // Union or intersection - if ( - param.type === AST_NODE_TYPES.Identifier && - (param.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSUnionType || - param.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSIntersectionType) - ) { - return param.typeAnnotation.typeAnnotation.types.filter( - type => type.type === AST_NODE_TYPES.TSTypeReference, - ) as TSESTree.TSTypeReference[]; - } - - // Tuple - if ( - param.type === AST_NODE_TYPES.ArrayPattern && - param.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES.TSTupleType - ) { - return param.typeAnnotation.typeAnnotation.elementTypes.filter( - type => type.type === AST_NODE_TYPES.TSTypeReference, - ) as TSESTree.TSTypeReference[]; - } - - // Inline object - if ( - param.type === AST_NODE_TYPES.ObjectPattern && - param.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSTypeLiteral - ) { - return param.typeAnnotation.typeAnnotation.members.reduce< - TSESTree.TSTypeReference[] - >((acc, member) => { - if ( - member.type === AST_NODE_TYPES.TSPropertySignature && - member.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSTypeReference - ) { - acc.push(member.typeAnnotation.typeAnnotation); - } - - return acc; - }, []); - } - - // Rest params - if ( - param.type === AST_NODE_TYPES.RestElement && - param.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSArrayType && - param.typeAnnotation.typeAnnotation.elementType.type === - AST_NODE_TYPES.TSTypeReference - ) { - return [param.typeAnnotation.typeAnnotation.elementType]; - } - - // Default value assignment - if ( - param.type === AST_NODE_TYPES.AssignmentPattern && - param.left.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSTypeReference - ) { - return [param.left.typeAnnotation.typeAnnotation]; - } - - return []; - } - - function getReturnTypeTypeNodes( - typeAnnotation: TSESTree.TSTypeAnnotation, - ): TSESTree.TSTypeReference[] { - // Single type - if ( - typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference - ) { - return [typeAnnotation.typeAnnotation]; - } - - // Union or intersection - if ( - typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSUnionType || - typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSIntersectionType - ) { - return typeAnnotation.typeAnnotation.types.filter( - type => type.type === AST_NODE_TYPES.TSTypeReference, - ) as TSESTree.TSTypeReference[]; - } - - // Tuple - if (typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSTupleType) { - return typeAnnotation.typeAnnotation.elementTypes.filter( - type => type.type === AST_NODE_TYPES.TSTypeReference, - ) as TSESTree.TSTypeReference[]; - } - - // Inline object - if (typeAnnotation.typeAnnotation.type === AST_NODE_TYPES.TSTypeLiteral) { - return typeAnnotation.typeAnnotation.members.reduce< - TSESTree.TSTypeReference[] - >((acc, member) => { - if ( - member.type === AST_NODE_TYPES.TSPropertySignature && - member.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSTypeReference - ) { - acc.push(member.typeAnnotation.typeAnnotation); - } - - return acc; - }, []); - } - - return []; - } - - function convertGenericTypeToTypeReferences( - functionNode: FunctionNode, - typeNode: TSESTree.TSTypeReference, - ): TSESTree.TSTypeReference | TSESTree.TSTypeReference[] { - const typeName = getTypeName(typeNode); - - if (!typeName) { - return typeNode; - } - - const scope = context.sourceCode.getScope(functionNode); - const variable = scope.set.get(typeName); - - if (!variable?.isTypeVariable) { - return typeNode; - } - - for (const def of variable.defs) { - if ( - def.type !== DefinitionType.Type || - def.node.type !== AST_NODE_TYPES.TSTypeParameter || - !def.node.constraint - ) { - continue; - } - - switch (def.node.constraint.type) { - // T extends SomeType - case AST_NODE_TYPES.TSTypeReference: - return def.node.constraint; - - // T extends SomeType | AnotherType - // T extends SomeType & AnotherType - case AST_NODE_TYPES.TSUnionType: - case AST_NODE_TYPES.TSIntersectionType: - return def.node.constraint.types.filter( - type => type.type === AST_NODE_TYPES.TSTypeReference, - ) as TSESTree.TSTypeReference[]; - - // T extends [SomeType, AnotherType] - case AST_NODE_TYPES.TSTupleType: - return def.node.constraint.elementTypes.filter( - type => type.type === AST_NODE_TYPES.TSTypeReference, - ) as TSESTree.TSTypeReference[]; - - // T extends { some: SomeType, another: AnotherType } - case AST_NODE_TYPES.TSTypeLiteral: - return def.node.constraint.members.reduce< - TSESTree.TSTypeReference[] - >((acc, member) => { - if ( - member.type === AST_NODE_TYPES.TSPropertySignature && - member.typeAnnotation?.typeAnnotation.type === - AST_NODE_TYPES.TSTypeReference - ) { - acc.push(member.typeAnnotation.typeAnnotation); - } - - return acc; - }, []); - } - } - - return typeNode; - } - function getTypeName(typeReference: TSESTree.TSTypeReference): string { if (typeReference.typeName.type === AST_NODE_TYPES.Identifier) { return typeReference.typeName.name; @@ -355,7 +143,7 @@ export default createRule<[], MessageIds>({ } return { - 'ImportDeclaration[importKind="type"] ImportSpecifier, ImportSpecifier[importKind="type"]': + 'ImportDeclaration ImportSpecifier, ImportSpecifier': collectImportedTypes, 'ExportNamedDeclaration TSTypeAliasDeclaration, ExportNamedDeclaration TSInterfaceDeclaration': diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index c83b3b893413..6d05d62a244b 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -268,6 +268,12 @@ ruleTester.run('require-types-exports', rule, { export function f(a: Arg): void {} `, + ` + import { Arg } from './types'; + + export function f(a: Arg): void {} + `, + ` import type { Arg } from './types'; @@ -1594,6 +1600,48 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type Arg1 = number; + type Arg2 = boolean; + type Ret = string; + + export declare function f( + a: { b: { c: Arg1 | number | { d: T } } }, + e: Arg1, + ): { a: { b: T | Ret } }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 45, + endColumn: 49, + data: { + name: 'Arg2', + }, + }, + { + messageId: 'requireTypeExport', + line: 7, + column: 24, + endColumn: 28, + data: { + name: 'Arg1', + }, + }, + { + messageId: 'requireTypeExport', + line: 9, + column: 26, + endColumn: 29, + data: { + name: 'Ret', + }, + }, + ], + }, + { code: ` type Arg1 = number; From 4bee779c4082e68cad2713f5e85663e95c5d62b2 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 24 Apr 2024 12:29:15 +0300 Subject: [PATCH 18/86] fix docs --- .../docs/rules/require-types-exports.md | 13 +++++++------ .../src/rules/require-types-exports.ts | 3 +-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/require-types-exports.md b/packages/eslint-plugin/docs/rules/require-types-exports.md index 767050deeba0..9343536e6d74 100644 --- a/packages/eslint-plugin/docs/rules/require-types-exports.md +++ b/packages/eslint-plugin/docs/rules/require-types-exports.md @@ -1,16 +1,17 @@ --- -description: 'Require exporting types that are used in exported functions declarations.' +description: 'Require exporting types that are used in exported entities.' --- > 🛑 This file is source code, not the primary documentation location! 🛑 > > See **https://typescript-eslint.io/rules/require-types-exports** for documentation. -When exporting functions from a module, it is recommended to export also all the -types that are used in the function declarations. This is useful for consumers of -the module, as it allows them to use the types in their own code without having to -use things like [`Parameters`](https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype) -or [`ReturnType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype) to extract the types from the function. +When exporting entities from a module, it is recommended to export also all the +types that are used in their declarations. This is useful for consumers of the +module, as it allows them to use the types in their own code without having to +use utility types like [`Parameters`](https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype) +or [`ReturnType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype) +in order to extract the types from your code. ## Examples diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index b99ba0ea7102..e5cde3687c4d 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -19,8 +19,7 @@ export default createRule<[], MessageIds>({ docs: { recommended: 'strict', requiresTypeChecking: true, - description: - 'Require exporting types that are used in exported functions declarations', + description: 'Require exporting types that are used in exported entities', }, messages: { requireTypeExport: 'Expected type "{{ name }}" to be exported', From 26e7be7153ebb95c8f69040a9132f82472192ed2 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 24 Apr 2024 12:47:42 +0300 Subject: [PATCH 19/86] add inferred return type test case --- .../tests/rules/require-types-exports.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 6d05d62a244b..63d36fd3fd16 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -291,6 +291,18 @@ ruleTester.run('require-types-exports', rule, { export function f(a: T): void {} `, + + ` + export type R = number; + + export function f() { + const value: { num: R } = { + num: 1, + }; + + return value; + } + `, ], invalid: [ @@ -1600,6 +1612,31 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type R = number; + + export function f() { + const value: { num: R } = { + num: 1, + }; + + return value; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 32, + data: { + name: 'R', + }, + }, + ], + }, + { code: ` type Arg1 = number; From e57985a6fdbd710c8deffd8ad34b52054e3b8422 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 24 Apr 2024 16:21:43 +0300 Subject: [PATCH 20/86] stupidly check for variable types --- .../src/rules/require-types-exports.ts | 68 +++++++++++++++++++ .../tests/rules/require-types-exports.test.ts | 58 ++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index e5cde3687c4d..3a803a25f8a9 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,3 +1,4 @@ +import type { Reference } from '@typescript-eslint/scope-manager'; import { DefinitionType } from '@typescript-eslint/scope-manager'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; @@ -12,6 +13,12 @@ type FunctionNode = | TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression; +type TypeReference = Reference & { + identifier: { + parent: TSESTree.TSTypeReference; + }; +}; + export default createRule<[], MessageIds>({ name: 'require-types-exports', meta: { @@ -28,9 +35,23 @@ export default createRule<[], MessageIds>({ }, defaultOptions: [], create(context) { + const typeReferences = new Set(); const externalizedTypes = new Set(); const reportedTypes = new Set(); + function collectTypeReferences(node: TSESTree.Program): void { + const scope = context.sourceCode.getScope(node); + + scope.references.forEach(r => { + if ( + r.resolved?.isTypeVariable && + r.identifier.parent.type === AST_NODE_TYPES.TSTypeReference + ) { + typeReferences.add(r as TypeReference); + } + }); + } + function collectImportedTypes(node: TSESTree.ImportSpecifier): void { externalizedTypes.add(node.local.name); } @@ -60,6 +81,8 @@ export default createRule<[], MessageIds>({ for (const declaration of node.declaration.declarations) { if (declaration.init?.type === AST_NODE_TYPES.ArrowFunctionExpression) { checkFunctionTypes(declaration.init); + } else { + checkVariableTypes(declaration); } } } @@ -107,6 +130,21 @@ export default createRule<[], MessageIds>({ .forEach(checkTypeNode); } + function checkVariableTypes( + node: TSESTree.LetOrConstOrVarDeclarator, + ): void { + if (node.id.type !== AST_NODE_TYPES.Identifier) { + return; + } + + typeReferences.forEach(r => { + // TODO: Probably not the best way to do it... + if (isLocationOverlapping(r.identifier.loc, node.loc)) { + checkTypeNode(r.identifier.parent); + } + }); + } + function checkTypeNode(node: TSESTree.TSTypeReference): void { const name = getTypeName(node); @@ -141,7 +179,37 @@ export default createRule<[], MessageIds>({ return ''; } + function isLocationOverlapping( + location: TSESTree.Node['loc'], + container: TSESTree.Node['loc'], + ): boolean { + if ( + location.start.line < container.start.line || + location.end.line > container.end.line + ) { + return false; + } + + if ( + location.start.line === container.start.line && + location.start.column < container.start.column + ) { + return false; + } + + if ( + location.end.line === container.end.line && + location.end.column > container.end.column + ) { + return false; + } + + return true; + } + return { + Program: collectTypeReferences, + 'ImportDeclaration ImportSpecifier, ImportSpecifier': collectImportedTypes, diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 63d36fd3fd16..39f66ebf2359 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -303,6 +303,24 @@ ruleTester.run('require-types-exports', rule, { 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, + }, + }, + }; + `, ], invalid: [ @@ -1739,5 +1757,45 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + import type { A } from './types'; + + type T1 = number; + + interface T2 { + key: number; + } + + export const value: { a: { b: { c: T1 } } } | [string, T2 | A] = { + a: { + b: { + c: 1, + }, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 10, + column: 44, + endColumn: 46, + data: { + name: 'T1', + }, + }, + { + messageId: 'requireTypeExport', + line: 10, + column: 64, + endColumn: 66, + data: { + name: 'T2', + }, + }, + ], + }, ], }); From cbb784c09a1aef1864d76c2cce72d2a0140d4fb6 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 24 Apr 2024 18:09:19 +0300 Subject: [PATCH 21/86] support default exported variable --- .../src/rules/require-types-exports.ts | 15 ++--- .../tests/rules/require-types-exports.test.ts | 62 +++++++++++++++++++ 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 3a803a25f8a9..62b64785dfb9 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -108,12 +108,17 @@ export default createRule<[], MessageIds>({ } for (const def of variable.defs) { + if (def.type !== DefinitionType.Variable || !def.node.init) { + continue; + } + if ( - def.type === DefinitionType.Variable && - (def.node.init?.type === AST_NODE_TYPES.ArrowFunctionExpression || - def.node.init?.type === AST_NODE_TYPES.FunctionExpression) + def.node.init.type === AST_NODE_TYPES.ArrowFunctionExpression || + def.node.init.type === AST_NODE_TYPES.FunctionExpression ) { checkFunctionTypes(def.node.init); + } else { + checkVariableTypes(def.node); } } } @@ -133,10 +138,6 @@ export default createRule<[], MessageIds>({ function checkVariableTypes( node: TSESTree.LetOrConstOrVarDeclarator, ): void { - if (node.id.type !== AST_NODE_TYPES.Identifier) { - return; - } - typeReferences.forEach(r => { // TODO: Probably not the best way to do it... if (isLocationOverlapping(r.identifier.loc, node.loc)) { diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 39f66ebf2359..857cc404b714 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -321,6 +321,26 @@ ruleTester.run('require-types-exports', rule, { }, }; `, + + ` + 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; + `, ], invalid: [ @@ -1797,5 +1817,47 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + import type { A } from './types'; + + type T1 = number; + + interface T2 { + key: number; + } + + const value: { a: { b: { c: T1 } } } | [string, T2 | A] = { + a: { + b: { + c: 1, + }, + }, + }; + + export default value; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 10, + column: 37, + endColumn: 39, + data: { + name: 'T1', + }, + }, + { + messageId: 'requireTypeExport', + line: 10, + column: 57, + endColumn: 59, + data: { + name: 'T2', + }, + }, + ], + }, ], }); From 6fb274af938403379ede6305350c5827ddedc879 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 19 May 2024 21:43:16 +0300 Subject: [PATCH 22/86] update docs --- ...-types-exports.md => require-types-exports.mdx} | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) rename packages/eslint-plugin/docs/rules/{require-types-exports.md => require-types-exports.mdx} (90%) diff --git a/packages/eslint-plugin/docs/rules/require-types-exports.md b/packages/eslint-plugin/docs/rules/require-types-exports.mdx similarity index 90% rename from packages/eslint-plugin/docs/rules/require-types-exports.md rename to packages/eslint-plugin/docs/rules/require-types-exports.mdx index 9343536e6d74..baa939fc2115 100644 --- a/packages/eslint-plugin/docs/rules/require-types-exports.md +++ b/packages/eslint-plugin/docs/rules/require-types-exports.mdx @@ -2,6 +2,9 @@ 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. @@ -15,9 +18,8 @@ in order to extract the types from your code. ## Examples - - -### ❌ Incorrect + + ```ts type Arg = string; @@ -43,7 +45,8 @@ enum Color { export declare function getRandomColor(): Color; ``` -### ✅ Correct + + ```ts export type Arg = string; @@ -69,7 +72,8 @@ export enum Color { export declare function getRandomColor(): Color; ``` - + + ## When Not To Use It From 4672fe17f5137e35d597d874e420cd39e7b424f3 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 19 May 2024 21:56:38 +0300 Subject: [PATCH 23/86] wip --- .../src/rules/require-types-exports.ts | 9 ++- .../require-types-exports.shot | 59 +++++++++++++++++++ .../tests/rules/require-types-exports.test.ts | 10 ++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-types-exports.shot diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 62b64785dfb9..bb7646656239 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -24,9 +24,9 @@ export default createRule<[], MessageIds>({ meta: { type: 'suggestion', docs: { + description: 'Require exporting types that are used in exported entities', recommended: 'strict', requiresTypeChecking: true, - description: 'Require exporting types that are used in exported entities', }, messages: { requireTypeExport: 'Expected type "{{ name }}" to be exported', @@ -57,7 +57,10 @@ export default createRule<[], MessageIds>({ } function collectExportedTypes( - node: TSESTree.TSTypeAliasDeclaration | TSESTree.TSInterfaceDeclaration, + node: + | TSESTree.TSTypeAliasDeclaration + | TSESTree.TSInterfaceDeclaration + | TSESTree.TSEnumDeclaration, ): void { externalizedTypes.add(node.id.name); } @@ -214,7 +217,7 @@ export default createRule<[], MessageIds>({ 'ImportDeclaration ImportSpecifier, ImportSpecifier': collectImportedTypes, - 'ExportNamedDeclaration TSTypeAliasDeclaration, ExportNamedDeclaration TSInterfaceDeclaration': + 'ExportNamedDeclaration TSTypeAliasDeclaration, ExportNamedDeclaration TSInterfaceDeclaration, ExportNamedDeclaration TSEnumDeclaration': collectExportedTypes, 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-types-exports.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-types-exports.shot new file mode 100644 index 000000000000..f515f012d362 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/require-types-exports.shot @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 1`] = ` +"Incorrect + +type Arg = string; +type Result = number; + +export function strLength(arg: Arg): Result { + ~~~ Expected type "Arg" to be exported + ~~~~~~ Expected type "Result" to be exported + return arg.length; +} + +interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; + ~~~~~ Expected type "Fruit" to be exported + +enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; + ~~~~~ Expected type "Color" to be exported +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 2`] = ` +"Correct + +export type Arg = string; +export type Result = number; + +export function strLength(arg: Arg): Result { + return arg.length; +} + +export interface Fruit { + name: string; + color: string; +} + +export const getFruitName = (fruit: Fruit) => fruit.name; + +export enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', +} + +export declare function getRandomColor(): Color; +" +`; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 857cc404b714..b2e8c2a35790 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -341,6 +341,16 @@ ruleTester.run('require-types-exports', rule, { export default value; `, + + ` + export enum Fruit { + Apple, + Banana, + Cherry, + } + + export function f(a: Fruit): void {} + `, ], invalid: [ From c79b5cbaa0d25d4164708eca04a61082b4785312 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 19 May 2024 22:09:44 +0300 Subject: [PATCH 24/86] wip --- packages/eslint-plugin/src/configs/strict-type-checked-only.ts | 1 + packages/typescript-eslint/src/configs/all.ts | 1 + packages/typescript-eslint/src/configs/disable-type-checked.ts | 1 + .../typescript-eslint/src/configs/strict-type-checked-only.ts | 1 + packages/typescript-eslint/src/configs/strict-type-checked.ts | 1 + 5 files changed, 5 insertions(+) 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 12709933dfb7..d5b62fb4fb70 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -43,6 +43,7 @@ export = { '@typescript-eslint/prefer-return-this-type': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': [ 'error', { diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index efe0d16fceb8..0579a6d7d89b 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -153,6 +153,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 8d2f29220aba..2c789c0df718 100644 --- a/packages/typescript-eslint/src/configs/disable-type-checked.ts +++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts @@ -60,6 +60,7 @@ export default ( '@typescript-eslint/promise-function-async': 'off', '@typescript-eslint/require-array-sort-compare': 'off', '@typescript-eslint/require-await': 'off', + '@typescript-eslint/require-types-exports': 'off', '@typescript-eslint/restrict-plus-operands': 'off', '@typescript-eslint/restrict-template-expressions': 'off', '@typescript-eslint/return-await': 'off', 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 f17b5280ca49..765371534b6a 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts @@ -52,6 +52,7 @@ export default ( '@typescript-eslint/prefer-return-this-type': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': [ 'error', { diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index ad62ee749e25..ddb7afd1db94 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -83,6 +83,7 @@ export default ( '@typescript-eslint/prefer-ts-expect-error': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': [ 'error', { From 279055a127fbfd01054d863f08dcf49cee4d5e34 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 19 May 2024 22:10:47 +0300 Subject: [PATCH 25/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index bb7646656239..7b7d3924d303 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -26,7 +26,6 @@ export default createRule<[], MessageIds>({ docs: { description: 'Require exporting types that are used in exported entities', recommended: 'strict', - requiresTypeChecking: true, }, messages: { requireTypeExport: 'Expected type "{{ name }}" to be exported', From 2f81933fb23c0c6d937db7b1ebe9c39fc6690d56 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 2 Jun 2024 22:14:30 +0300 Subject: [PATCH 26/86] improve types --- packages/eslint-plugin/src/rules/require-types-exports.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 7b7d3924d303..9236274ca627 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -14,6 +14,7 @@ type FunctionNode = | TSESTree.FunctionExpression; type TypeReference = Reference & { + isTypeReference: true; identifier: { parent: TSESTree.TSTypeReference; }; @@ -44,7 +45,9 @@ export default createRule<[], MessageIds>({ scope.references.forEach(r => { if ( r.resolved?.isTypeVariable && - r.identifier.parent.type === AST_NODE_TYPES.TSTypeReference + r.identifier.type === AST_NODE_TYPES.Identifier && + r.identifier.parent.type === AST_NODE_TYPES.TSTypeReference && + r.isTypeReference ) { typeReferences.add(r as TypeReference); } From 0f788d2e38afb2c3f6f704c7ac0df8d693e6548e Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 2 Jun 2024 22:15:18 +0300 Subject: [PATCH 27/86] improve type reference search --- .../src/rules/require-types-exports.ts | 48 ++++++---------- .../tests/rules/require-types-exports.test.ts | 57 +++++++++++++++++++ 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 9236274ca627..abdad788019c 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -144,13 +144,29 @@ export default createRule<[], MessageIds>({ node: TSESTree.LetOrConstOrVarDeclarator, ): void { typeReferences.forEach(r => { - // TODO: Probably not the best way to do it... - if (isLocationOverlapping(r.identifier.loc, node.loc)) { + if (isAncestorNode(node, r.identifier.parent)) { checkTypeNode(r.identifier.parent); } }); } + function isAncestorNode( + ancestor: TSESTree.Node, + node: TSESTree.Node, + ): boolean { + let parent = node.parent; + + while (parent) { + if (parent === ancestor) { + return true; + } + + parent = parent.parent; + } + + return false; + } + function checkTypeNode(node: TSESTree.TSTypeReference): void { const name = getTypeName(node); @@ -185,34 +201,6 @@ export default createRule<[], MessageIds>({ return ''; } - function isLocationOverlapping( - location: TSESTree.Node['loc'], - container: TSESTree.Node['loc'], - ): boolean { - if ( - location.start.line < container.start.line || - location.end.line > container.end.line - ) { - return false; - } - - if ( - location.start.line === container.start.line && - location.start.column < container.start.column - ) { - return false; - } - - if ( - location.end.line === container.end.line && - location.end.column > container.end.column - ) { - return false; - } - - return true; - } - return { Program: collectTypeReferences, diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index b2e8c2a35790..ec41d6d2e2a2 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -1869,5 +1869,62 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + type T1 = number; + + interface T2 { + key: number; + } + + type T3 = boolean; + + export const value: + | { + a: T1; + b: { + c: T2; + }; + } + | T3[] = { + a: 1, + b: { + c: { + key: 1, + }, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 12, + column: 18, + endColumn: 20, + data: { + name: 'T1', + }, + }, + { + messageId: 'requireTypeExport', + line: 14, + column: 20, + endColumn: 22, + data: { + name: 'T2', + }, + }, + { + messageId: 'requireTypeExport', + line: 17, + column: 13, + endColumn: 15, + data: { + name: 'T3', + }, + }, + ], + }, ], }); From 6cec0f56a80fdc10e57d240ac6cfd517efdec4ce Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 2 Jun 2024 22:42:13 +0300 Subject: [PATCH 28/86] don't report types from default library --- .../src/rules/require-types-exports.ts | 15 ++++++++++++++- .../tests/rules/require-types-exports.test.ts | 12 ++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index abdad788019c..a5af6ede8b53 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -3,7 +3,11 @@ import { DefinitionType } from '@typescript-eslint/scope-manager'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { + createRule, + getParserServices, + isSymbolFromDefaultLibrary, +} from '../util'; type MessageIds = 'requireTypeExport'; @@ -35,6 +39,8 @@ export default createRule<[], MessageIds>({ }, defaultOptions: [], create(context) { + const services = getParserServices(context); + const typeReferences = new Set(); const externalizedTypes = new Set(); const reportedTypes = new Set(); @@ -182,6 +188,13 @@ export default createRule<[], MessageIds>({ return; } + const tsNode = services.esTreeNodeToTSNodeMap.get(node); + const type = services.program.getTypeChecker().getTypeAtLocation(tsNode); + + if (isSymbolFromDefaultLibrary(services.program, type.getSymbol())) { + return; + } + context.report({ node: node, messageId: 'requireTypeExport', diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index ec41d6d2e2a2..c02324e22abd 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -351,6 +351,18 @@ ruleTester.run('require-types-exports', rule, { export function f(a: Fruit): void {} `, + + ` + export function f(arg: Record>) { + return arg; + } + `, + + ` + export function f>>(arg: T) { + return arg; + } + `, ], invalid: [ From 497957a7f9df3a96285ea5a5c09231738a58cee6 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 2 Jun 2024 23:21:47 +0300 Subject: [PATCH 29/86] getTypeName --- .../src/rules/require-types-exports.ts | 22 +++++++---- .../tests/rules/require-types-exports.test.ts | 39 +++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index a5af6ede8b53..00a68f732ef5 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -174,10 +174,11 @@ export default createRule<[], MessageIds>({ } function checkTypeNode(node: TSESTree.TSTypeReference): void { - const name = getTypeName(node); + const name = getTypeName(node.typeName); - if (!name) { - // TODO: Report the whole function? Is this case even possible? + // Using `this` type is allowed since it's necessarily exported + // if it's used in an exported entity. + if (name === 'this') { return; } @@ -206,12 +207,17 @@ export default createRule<[], MessageIds>({ reportedTypes.add(name); } - function getTypeName(typeReference: TSESTree.TSTypeReference): string { - if (typeReference.typeName.type === AST_NODE_TYPES.Identifier) { - return typeReference.typeName.name; - } + function getTypeName(typeName: TSESTree.EntityName): string { + switch (typeName.type) { + case AST_NODE_TYPES.Identifier: + return typeName.name; + + case AST_NODE_TYPES.TSQualifiedName: + return getTypeName(typeName.left) + '.' + typeName.right.name; - return ''; + case AST_NODE_TYPES.ThisExpression: + return 'this'; + } } return { diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index c02324e22abd..d7b597a4d9d6 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -363,6 +363,22 @@ ruleTester.run('require-types-exports', rule, { return arg; } `, + + ` + export class Wrapper { + work(other: this) {} + } + `, + + ` + export namespace A { + export type B = number; + } + + export function a(arg: A.B) { + return arg; + } + `, ], invalid: [ @@ -1800,6 +1816,29 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + export namespace A { + type B = number; + } + + export function a(arg: A.B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 32, + endColumn: 35, + data: { + name: 'A.B', + }, + }, + ], + }, + { code: ` import type { A } from './types'; From 700ff85cf3c74fd2cc3547328ea3e4af14d085e1 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Tue, 4 Jun 2024 21:07:27 +0300 Subject: [PATCH 30/86] move utils out of the closure --- .../src/rules/require-types-exports.ts | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 00a68f732ef5..02b2a224cd76 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -156,23 +156,6 @@ export default createRule<[], MessageIds>({ }); } - function isAncestorNode( - ancestor: TSESTree.Node, - node: TSESTree.Node, - ): boolean { - let parent = node.parent; - - while (parent) { - if (parent === ancestor) { - return true; - } - - parent = parent.parent; - } - - return false; - } - function checkTypeNode(node: TSESTree.TSTypeReference): void { const name = getTypeName(node.typeName); @@ -207,19 +190,6 @@ export default createRule<[], MessageIds>({ reportedTypes.add(name); } - function getTypeName(typeName: TSESTree.EntityName): string { - switch (typeName.type) { - case AST_NODE_TYPES.Identifier: - return typeName.name; - - case AST_NODE_TYPES.TSQualifiedName: - return getTypeName(typeName.left) + '.' + typeName.right.name; - - case AST_NODE_TYPES.ThisExpression: - return 'this'; - } - } - return { Program: collectTypeReferences, @@ -249,3 +219,30 @@ export default createRule<[], MessageIds>({ }; }, }); + +function getTypeName(typeName: TSESTree.EntityName): string { + switch (typeName.type) { + case AST_NODE_TYPES.Identifier: + return typeName.name; + + case AST_NODE_TYPES.TSQualifiedName: + return getTypeName(typeName.left) + '.' + typeName.right.name; + + case AST_NODE_TYPES.ThisExpression: + return 'this'; + } +} + +function isAncestorNode(ancestor: TSESTree.Node, node: TSESTree.Node): boolean { + let parent = node.parent; + + while (parent) { + if (parent === ancestor) { + return true; + } + + parent = parent.parent; + } + + return false; +} From 9a155b3058d37bc76fd41afb4c7e8d97aab471f9 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 5 Jun 2024 21:32:33 +0300 Subject: [PATCH 31/86] support namespaced types --- .../src/rules/require-types-exports.ts | 51 +++++++++++++++---- .../tests/rules/require-types-exports.test.ts | 20 +++++--- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 02b2a224cd76..fa5565d4533a 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -137,13 +137,13 @@ export default createRule<[], MessageIds>({ function checkFunctionTypes(node: FunctionNode): void { const scope = context.sourceCode.getScope(node); - scope.through - .map(ref => ref.identifier.parent) - .filter( - (node): node is TSESTree.TSTypeReference => - node.type === AST_NODE_TYPES.TSTypeReference, - ) - .forEach(checkTypeNode); + scope.through.forEach(ref => { + const typeRef = findClosestTypeReference(ref.identifier, node); + + if (typeRef) { + checkTypeNode(typeRef); + } + }); } function checkVariableTypes( @@ -157,7 +157,7 @@ export default createRule<[], MessageIds>({ } function checkTypeNode(node: TSESTree.TSTypeReference): void { - const name = getTypeName(node.typeName); + let name = getTypeName(node.typeName); // Using `this` type is allowed since it's necessarily exported // if it's used in an exported entity. @@ -165,6 +165,12 @@ export default createRule<[], MessageIds>({ return; } + // Namespaced types are not exported directly, so we check the + // leftmost part of the name. + if (Array.isArray(name)) { + name = name[0]; + } + const isExternalized = externalizedTypes.has(name); const isReported = reportedTypes.has(name); @@ -196,7 +202,13 @@ export default createRule<[], MessageIds>({ 'ImportDeclaration ImportSpecifier, ImportSpecifier': collectImportedTypes, - 'ExportNamedDeclaration TSTypeAliasDeclaration, ExportNamedDeclaration TSInterfaceDeclaration, ExportNamedDeclaration TSEnumDeclaration': + 'Program > ExportNamedDeclaration > TSTypeAliasDeclaration': + collectExportedTypes, + 'Program > ExportNamedDeclaration > TSInterfaceDeclaration': + collectExportedTypes, + 'Program > ExportNamedDeclaration > TSEnumDeclaration': + collectExportedTypes, + 'Program > ExportNamedDeclaration > TSModuleDeclaration': collectExportedTypes, 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': @@ -220,19 +232,36 @@ export default createRule<[], MessageIds>({ }, }); -function getTypeName(typeName: TSESTree.EntityName): string { +function getTypeName(typeName: TSESTree.EntityName): string | string[] { switch (typeName.type) { case AST_NODE_TYPES.Identifier: return typeName.name; case AST_NODE_TYPES.TSQualifiedName: - return getTypeName(typeName.left) + '.' + typeName.right.name; + return [...(getTypeName(typeName.left) || []), typeName.right.name]; case AST_NODE_TYPES.ThisExpression: return 'this'; } } +function findClosestTypeReference( + startNode: TSESTree.Node, + endNode: TSESTree.Node, +): TSESTree.TSTypeReference | null { + let parent = startNode.parent; + + while (parent && parent !== endNode) { + if (parent.type === AST_NODE_TYPES.TSTypeReference) { + return parent; + } + + parent = parent.parent; + } + + return null; +} + function isAncestorNode(ancestor: TSESTree.Node, node: TSESTree.Node): boolean { let parent = node.parent; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index d7b597a4d9d6..6a3008c3f29f 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -372,10 +372,12 @@ ruleTester.run('require-types-exports', rule, { ` export namespace A { - export type B = number; + export namespace B { + export type C = number; + } } - export function a(arg: A.B) { + export function a(arg: A.B.C) { return arg; } `, @@ -1818,22 +1820,24 @@ ruleTester.run('require-types-exports', rule, { { code: ` - export namespace A { - type B = number; + namespace A { + export namespace B { + export type C = number; + } } - export function a(arg: A.B) { + export function a(arg: A.B.C) { return arg; } `, errors: [ { messageId: 'requireTypeExport', - line: 6, + line: 8, column: 32, - endColumn: 35, + endColumn: 37, data: { - name: 'A.B', + name: 'A', }, }, ], From b65f9c474efadcbd304ca0757c60dedd5e249879 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 5 Jun 2024 21:54:23 +0300 Subject: [PATCH 32/86] fix namespaced imports --- .../src/rules/require-types-exports.ts | 14 ++++++++++---- .../tests/rules/require-types-exports.test.ts | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index fa5565d4533a..ae28c9d03711 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -199,8 +199,9 @@ export default createRule<[], MessageIds>({ return { Program: collectTypeReferences, - 'ImportDeclaration ImportSpecifier, ImportSpecifier': - collectImportedTypes, + 'ImportDeclaration ImportSpecifier': collectImportedTypes, + 'ImportDeclaration ImportNamespaceSpecifier': collectImportedTypes, + 'ImportDeclaration ImportDefaultSpecifier': collectImportedTypes, 'Program > ExportNamedDeclaration > TSTypeAliasDeclaration': collectExportedTypes, @@ -237,8 +238,13 @@ function getTypeName(typeName: TSESTree.EntityName): string | string[] { case AST_NODE_TYPES.Identifier: return typeName.name; - case AST_NODE_TYPES.TSQualifiedName: - return [...(getTypeName(typeName.left) || []), typeName.right.name]; + case AST_NODE_TYPES.TSQualifiedName: { + let left = getTypeName(typeName.left); + + left = Array.isArray(left) ? left : [left]; + + return [...left, typeName.right.name]; + } case AST_NODE_TYPES.ThisExpression: return 'this'; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 6a3008c3f29f..86da0ca52e91 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -381,6 +381,22 @@ ruleTester.run('require-types-exports', rule, { 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; + } + `, ], invalid: [ From 078e24afcc6d8695367738fd163dfc8e20916605 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 5 Jun 2024 22:16:57 +0300 Subject: [PATCH 33/86] WIP --- packages/eslint-plugin/src/rules/require-types-exports.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index ae28c9d03711..a812151985e6 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -60,7 +60,12 @@ export default createRule<[], MessageIds>({ }); } - function collectImportedTypes(node: TSESTree.ImportSpecifier): void { + function collectImportedTypes( + node: + | TSESTree.ImportSpecifier + | TSESTree.ImportNamespaceSpecifier + | TSESTree.ImportDefaultSpecifier, + ): void { externalizedTypes.add(node.local.name); } From ed23162a443c55cebcae9c716fd20e04973f1f98 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 5 Jun 2024 22:45:56 +0300 Subject: [PATCH 34/86] wip --- .../src/rules/require-types-exports.ts | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index a812151985e6..a3def00e15ea 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -154,15 +154,15 @@ export default createRule<[], MessageIds>({ function checkVariableTypes( node: TSESTree.LetOrConstOrVarDeclarator, ): void { - typeReferences.forEach(r => { - if (isAncestorNode(node, r.identifier.parent)) { - checkTypeNode(r.identifier.parent); + typeReferences.forEach(ref => { + if (isAncestorNode(node, ref.identifier.parent)) { + checkTypeNode(ref.identifier.parent); } }); } function checkTypeNode(node: TSESTree.TSTypeReference): void { - let name = getTypeName(node.typeName); + const name = getTypeName(node.typeName); // Using `this` type is allowed since it's necessarily exported // if it's used in an exported entity. @@ -170,12 +170,6 @@ export default createRule<[], MessageIds>({ return; } - // Namespaced types are not exported directly, so we check the - // leftmost part of the name. - if (Array.isArray(name)) { - name = name[0]; - } - const isExternalized = externalizedTypes.has(name); const isReported = reportedTypes.has(name); @@ -238,18 +232,15 @@ export default createRule<[], MessageIds>({ }, }); -function getTypeName(typeName: TSESTree.EntityName): string | string[] { +function getTypeName(typeName: TSESTree.EntityName): string { switch (typeName.type) { case AST_NODE_TYPES.Identifier: return typeName.name; - case AST_NODE_TYPES.TSQualifiedName: { - let left = getTypeName(typeName.left); - - left = Array.isArray(left) ? left : [left]; - - return [...left, typeName.right.name]; - } + case AST_NODE_TYPES.TSQualifiedName: + // Namespaced types are not exported directly, so we check the + // leftmost part of the name. + return getTypeName(typeName.left); case AST_NODE_TYPES.ThisExpression: return 'this'; From ac224eb6107ba236204298b4a6e325bb9b61c7e5 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 5 Jun 2024 22:46:07 +0300 Subject: [PATCH 35/86] fix propertykey tests --- packages/eslint-plugin/src/rules/require-types-exports.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index a3def00e15ea..420ca23f811e 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -179,8 +179,9 @@ export default createRule<[], MessageIds>({ const tsNode = services.esTreeNodeToTSNodeMap.get(node); const type = services.program.getTypeChecker().getTypeAtLocation(tsNode); + const symbol = type.aliasSymbol ?? type.getSymbol(); - if (isSymbolFromDefaultLibrary(services.program, type.getSymbol())) { + if (isSymbolFromDefaultLibrary(services.program, symbol)) { return; } From 417cc91ea799917a3c398805ecf16eef018cd765 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Wed, 5 Jun 2024 22:55:34 +0300 Subject: [PATCH 36/86] ReturnType test --- .../eslint-plugin/tests/rules/require-types-exports.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 86da0ca52e91..e58a6c55bc0b 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -364,6 +364,12 @@ ruleTester.run('require-types-exports', rule, { } `, + ` + export function f string>>(arg: T) { + return arg; + } + `, + ` export class Wrapper { work(other: this) {} From ae1b87c3474b4d31834f973fc07cbbeee055b295 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Fri, 28 Jun 2024 13:31:19 +0300 Subject: [PATCH 37/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 420ca23f811e..4ef1eed15f9d 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -155,7 +155,9 @@ export default createRule<[], MessageIds>({ node: TSESTree.LetOrConstOrVarDeclarator, ): void { typeReferences.forEach(ref => { - if (isAncestorNode(node, ref.identifier.parent)) { + const isTypeUsedInNode = isAncestorNode(node, ref.identifier.parent); + + if (isTypeUsedInNode) { checkTypeNode(ref.identifier.parent); } }); From d22740832b61459f6a9faccadb707cffe725d2ef Mon Sep 17 00:00:00 2001 From: StyleShit Date: Fri, 28 Jun 2024 18:09:12 +0300 Subject: [PATCH 38/86] collect type references recursively --- .../src/rules/require-types-exports.ts | 241 ++++--- .../tests/rules/require-types-exports.test.ts | 603 ++++++++++++++++++ 2 files changed, 743 insertions(+), 101 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 4ef1eed15f9d..44ccdee972da 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,6 +1,5 @@ -import type { Reference } from '@typescript-eslint/scope-manager'; import { DefinitionType } from '@typescript-eslint/scope-manager'; -import type { TSESTree } from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { @@ -11,19 +10,6 @@ import { type MessageIds = 'requireTypeExport'; -type FunctionNode = - | TSESTree.FunctionDeclaration - | TSESTree.TSDeclareFunction - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionExpression; - -type TypeReference = Reference & { - isTypeReference: true; - identifier: { - parent: TSESTree.TSTypeReference; - }; -}; - export default createRule<[], MessageIds>({ name: 'require-types-exports', meta: { @@ -41,25 +27,9 @@ export default createRule<[], MessageIds>({ create(context) { const services = getParserServices(context); - const typeReferences = new Set(); const externalizedTypes = new Set(); const reportedTypes = new Set(); - function collectTypeReferences(node: TSESTree.Program): void { - const scope = context.sourceCode.getScope(node); - - scope.references.forEach(r => { - if ( - r.resolved?.isTypeVariable && - r.identifier.type === AST_NODE_TYPES.Identifier && - r.identifier.parent.type === AST_NODE_TYPES.TSTypeReference && - r.isTypeReference - ) { - typeReferences.add(r as TypeReference); - } - }); - } - function collectImportedTypes( node: | TSESTree.ImportSpecifier @@ -82,11 +52,12 @@ export default createRule<[], MessageIds>({ node: ( | TSESTree.ExportNamedDeclaration | TSESTree.DefaultExportDeclarations + | TSESTree.ArrowFunctionExpression ) & { declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; }, ): void { - checkFunctionTypes(node.declaration); + checkNodeTypes(node.declaration); } function visitExportedVariableDeclaration( @@ -95,22 +66,10 @@ export default createRule<[], MessageIds>({ }, ): void { for (const declaration of node.declaration.declarations) { - if (declaration.init?.type === AST_NODE_TYPES.ArrowFunctionExpression) { - checkFunctionTypes(declaration.init); - } else { - checkVariableTypes(declaration); - } + checkNodeTypes(declaration); } } - function visitDefaultExportedArrowFunction( - node: TSESTree.ExportDefaultDeclaration & { - declaration: TSESTree.ArrowFunctionExpression; - }, - ): void { - checkFunctionTypes(node.declaration); - } - function visitDefaultExportedIdentifier( node: TSESTree.DefaultExportDeclarations & { declaration: TSESTree.Identifier; @@ -128,39 +87,17 @@ export default createRule<[], MessageIds>({ continue; } - if ( - def.node.init.type === AST_NODE_TYPES.ArrowFunctionExpression || - def.node.init.type === AST_NODE_TYPES.FunctionExpression - ) { - checkFunctionTypes(def.node.init); - } else { - checkVariableTypes(def.node); - } + checkNodeTypes(def.node); } } - function checkFunctionTypes(node: FunctionNode): void { - const scope = context.sourceCode.getScope(node); - - scope.through.forEach(ref => { - const typeRef = findClosestTypeReference(ref.identifier, node); + function checkNodeTypes(node: TSESTree.Node): void { + const typeReferences = getTypeReferencesRecursively( + node, + context.sourceCode, + ); - if (typeRef) { - checkTypeNode(typeRef); - } - }); - } - - function checkVariableTypes( - node: TSESTree.LetOrConstOrVarDeclarator, - ): void { - typeReferences.forEach(ref => { - const isTypeUsedInNode = isAncestorNode(node, ref.identifier.parent); - - if (isTypeUsedInNode) { - checkTypeNode(ref.identifier.parent); - } - }); + typeReferences.forEach(checkTypeNode); } function checkTypeNode(node: TSESTree.TSTypeReference): void { @@ -199,8 +136,6 @@ export default createRule<[], MessageIds>({ } return { - Program: collectTypeReferences, - 'ImportDeclaration ImportSpecifier': collectImportedTypes, 'ImportDeclaration ImportNamespaceSpecifier': collectImportedTypes, 'ImportDeclaration ImportDefaultSpecifier': collectImportedTypes, @@ -220,14 +155,14 @@ export default createRule<[], MessageIds>({ 'ExportNamedDeclaration[declaration.type="TSDeclareFunction"]': visitExportedFunctionDeclaration, - 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': - visitExportedVariableDeclaration, - 'ExportDefaultDeclaration[declaration.type="FunctionDeclaration"]': visitExportedFunctionDeclaration, 'ExportDefaultDeclaration[declaration.type="ArrowFunctionExpression"]': - visitDefaultExportedArrowFunction, + visitExportedFunctionDeclaration, + + 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': + visitExportedVariableDeclaration, 'ExportDefaultDeclaration[declaration.type="Identifier"]': visitDefaultExportedIdentifier, @@ -250,33 +185,137 @@ function getTypeName(typeName: TSESTree.EntityName): string { } } -function findClosestTypeReference( - startNode: TSESTree.Node, - endNode: TSESTree.Node, -): TSESTree.TSTypeReference | null { - let parent = startNode.parent; +function getTypeReferencesRecursively( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +): Set { + const typeReferences = new Set(); - while (parent && parent !== endNode) { - if (parent.type === AST_NODE_TYPES.TSTypeReference) { - return parent; - } + collect(node); - parent = parent.parent; - } + function collect(node: TSESTree.Node): void { + switch (node.type) { + case AST_NODE_TYPES.VariableDeclarator: + collect(node.id); - return null; -} + if (node.init) { + collect(node.init); + } + break; -function isAncestorNode(ancestor: TSESTree.Node, node: TSESTree.Node): boolean { - let parent = node.parent; + case AST_NODE_TYPES.Identifier: { + const typeAnnotation = node.typeAnnotation?.typeAnnotation; - while (parent) { - if (parent === ancestor) { - return true; - } + if (typeAnnotation) { + collect(typeAnnotation); + } + + // If it's a reference to a variable inside an object, we need to get the declared variable. + if ( + node.parent.type === AST_NODE_TYPES.Property || + node.parent.type === AST_NODE_TYPES.ArrayExpression + ) { + const variableNode = sourceCode.getScope(node).set.get(node.name); + + variableNode?.defs.forEach(def => { + collect(def.name); + collect(def.node); + }); + } + + break; + } + + case AST_NODE_TYPES.ObjectExpression: + node.properties.forEach(property => { + const nodeToCheck = + property.type === AST_NODE_TYPES.Property + ? property.value + : property.argument; + + collect(nodeToCheck); + }); + break; + + case AST_NODE_TYPES.ArrayExpression: + node.elements.forEach(element => { + if (!element) { + return; + } + + const nodeToCheck = + element.type === AST_NODE_TYPES.SpreadElement + ? element.argument + : element; + + collect(nodeToCheck); + }); + break; + + case AST_NODE_TYPES.ArrowFunctionExpression: + case AST_NODE_TYPES.FunctionDeclaration: + case AST_NODE_TYPES.FunctionExpression: + case AST_NODE_TYPES.TSDeclareFunction: { + const scope = sourceCode.getScope(node); + + scope.through.forEach(ref => { + collect(ref.identifier.parent); + }); + break; + } - parent = parent.parent; + case AST_NODE_TYPES.TSTypeReference: + typeReferences.add(node); + + node.typeArguments?.params.forEach(param => collect(param)); + break; + + case AST_NODE_TYPES.TSArrayType: + collect(node.elementType); + break; + + case AST_NODE_TYPES.TSTupleType: + node.elementTypes.forEach(element => collect(element)); + break; + + case AST_NODE_TYPES.TSUnionType: + case AST_NODE_TYPES.TSIntersectionType: + node.types.forEach(type => collect(type)); + break; + + case AST_NODE_TYPES.TSTypeLiteral: + node.members.forEach(member => collect(member)); + break; + + case AST_NODE_TYPES.TSPropertySignature: + if (node.typeAnnotation?.typeAnnotation) { + collect(node.typeAnnotation.typeAnnotation); + } + break; + + case AST_NODE_TYPES.TSQualifiedName: + collect(node.parent); + break; + + case AST_NODE_TYPES.TSAsExpression: { + collect(node.expression); + + const isAsConstAnnotation = + node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && + node.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && + node.typeAnnotation.typeName.name === 'const'; + + if (!isAsConstAnnotation) { + collect(node.typeAnnotation); + } + + break; + } + + default: + break; + } } - return false; + return typeReferences; } diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index e58a6c55bc0b..7c81df01dd38 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -2003,5 +2003,608 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + type A = string; + type B = string; + + const apple: A = 'apple'; + const banana: B = 'banana'; + + export const value = { + path: { + to: { + apple, + and: { + banana, + }, + }, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 22, + endColumn: 23, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = string; + type B = string; + + const apple: A = 'apple'; + const banana: B = 'banana'; + + const value = { + path: { + to: { + apple, + and: { + banana, + }, + }, + }, + }; + + export default value; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 22, + endColumn: 23, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = string; + type B = string; + + const apple: A = 'apple'; + const banana: B = 'banana'; + + const value = { + spreadObject: { ...{ apple } }, + spreadArray: [...[banana]], + }; + + export default value; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 22, + endColumn: 23, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana], + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 22, + endColumn: 27, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana] as const, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 22, + endColumn: 27, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple: Fruit = 'apple'; + const banana: Fruit = 'banana'; + + export const value = { + path: { + to: [apple, banana] as any, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 22, + endColumn: 27, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple = 'apple'; + const banana = 'banana'; + + export const value = { + path: { + to: [apple, banana] as [Fruit, Fruit], + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 9, + column: 37, + endColumn: 42, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type Fruit = 'apple' | 'banana'; + + const apple = 'apple'; + const banana = 'banana'; + + export const value = { + path: { + to: [apple, banana] as Fruit | number, + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 9, + column: 36, + endColumn: 41, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + type C = boolean; + type D = symbol; + + declare const a: [A, B] | ([Array, Set] & number); + + export const value = { a }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 27, + endColumn: 28, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 7, + column: 30, + endColumn: 31, + data: { + name: 'B', + }, + }, + { + messageId: 'requireTypeExport', + line: 7, + column: 43, + endColumn: 44, + data: { + name: 'C', + }, + }, + { + messageId: 'requireTypeExport', + line: 7, + column: 51, + endColumn: 52, + data: { + name: 'D', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + export const value = { + func: (arg: A): B => 'apple', + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 27, + endColumn: 28, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + export const value = { + func: function (arg: A): B { + return 'apple'; + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 32, + endColumn: 33, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 36, + endColumn: 37, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + const func = (arg: A): B => 'apple'; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 28, + endColumn: 29, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + const func = function (arg: A): B { + return 'apple'; + }; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 38, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const func = (arg: T): T => 'apple'; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 33, + endColumn: 34, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const func = function (arg: T): T { + return 'apple'; + }; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 42, + endColumn: 43, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + export const value = { + func: (arg: T): T => 'apple', + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 28, + endColumn: 29, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + export const value = { + func: function (arg: T): T { + return 'apple'; + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 37, + endColumn: 38, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + declare function func(arg: T): T; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare function func(arg: T): T; + + export const value = { + func, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 41, + endColumn: 46, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + a, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 26, + endColumn: 37, + data: { + name: 'Fruit', + }, + }, + ], + }, ], }); From dca52d062cd0c390dedb72be97582204c7da9820 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Fri, 28 Jun 2024 18:49:50 +0300 Subject: [PATCH 39/86] lib types --- .../src/rules/require-types-exports.ts | 77 ++++++++++--------- .../tests/rules/require-types-exports.test.ts | 43 ++++++++++- 2 files changed, 83 insertions(+), 37 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 44ccdee972da..8cf70d1a13f5 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,12 +1,11 @@ -import { DefinitionType } from '@typescript-eslint/scope-manager'; +import { + DefinitionType, + ImplicitLibVariable, +} from '@typescript-eslint/scope-manager'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { - createRule, - getParserServices, - isSymbolFromDefaultLibrary, -} from '../util'; +import { createRule } from '../util'; type MessageIds = 'requireTypeExport'; @@ -25,8 +24,6 @@ export default createRule<[], MessageIds>({ }, defaultOptions: [], create(context) { - const services = getParserServices(context); - const externalizedTypes = new Set(); const reportedTypes = new Set(); @@ -76,7 +73,7 @@ export default createRule<[], MessageIds>({ }, ): void { const scope = context.sourceCode.getScope(node); - const variable = scope.set.get(node.declaration.name); + const variable = getVariable(node.declaration.name, scope); if (!variable) { return; @@ -103,12 +100,6 @@ export default createRule<[], MessageIds>({ function checkTypeNode(node: TSESTree.TSTypeReference): void { const name = getTypeName(node.typeName); - // Using `this` type is allowed since it's necessarily exported - // if it's used in an exported entity. - if (name === 'this') { - return; - } - const isExternalized = externalizedTypes.has(name); const isReported = reportedTypes.has(name); @@ -116,14 +107,6 @@ export default createRule<[], MessageIds>({ return; } - const tsNode = services.esTreeNodeToTSNodeMap.get(node); - const type = services.program.getTypeChecker().getTypeAtLocation(tsNode); - const symbol = type.aliasSymbol ?? type.getSymbol(); - - if (isSymbolFromDefaultLibrary(services.program, symbol)) { - return; - } - context.report({ node: node, messageId: 'requireTypeExport', @@ -210,12 +193,14 @@ function getTypeReferencesRecursively( collect(typeAnnotation); } - // If it's a reference to a variable inside an object, we need to get the declared variable. + // If it's a reference to a variable inside an object or an array, + // we need to get the declared variable. if ( node.parent.type === AST_NODE_TYPES.Property || node.parent.type === AST_NODE_TYPES.ArrayExpression ) { - const variableNode = sourceCode.getScope(node).set.get(node.name); + const scope = sourceCode.getScope(node); + const variableNode = getVariable(node.name, scope); variableNode?.defs.forEach(def => { collect(def.name); @@ -264,11 +249,19 @@ function getTypeReferencesRecursively( break; } - case AST_NODE_TYPES.TSTypeReference: - typeReferences.add(node); + case AST_NODE_TYPES.TSTypeReference: { + const scope = sourceCode.getScope(node); + const variable = getVariable(getTypeName(node.typeName), scope); + + const isBuiltinType = variable instanceof ImplicitLibVariable; + + if (!isBuiltinType) { + typeReferences.add(node); + } node.typeArguments?.params.forEach(param => collect(param)); break; + } case AST_NODE_TYPES.TSArrayType: collect(node.elementType); @@ -299,15 +292,7 @@ function getTypeReferencesRecursively( case AST_NODE_TYPES.TSAsExpression: { collect(node.expression); - - const isAsConstAnnotation = - node.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && - node.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && - node.typeAnnotation.typeName.name === 'const'; - - if (!isAsConstAnnotation) { - collect(node.typeAnnotation); - } + collect(node.typeAnnotation); break; } @@ -319,3 +304,23 @@ function getTypeReferencesRecursively( return typeReferences; } + +function getVariable( + name: string, + initialScope: TSESLint.Scope.Scope | null, +): TSESLint.Scope.Variable | null { + let variable: TSESLint.Scope.Variable | null = null; + let scope: TSESLint.Scope.Scope | null = initialScope; + + while (scope) { + variable = scope.set.get(name) ?? null; + + if (variable) { + break; + } + + scope = scope.upper; + } + + return variable; +} diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 7c81df01dd38..c80d0ee13039 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -403,6 +403,47 @@ ruleTester.run('require-types-exports', rule, { 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, + }; + `, ], invalid: [ @@ -2262,7 +2303,7 @@ ruleTester.run('require-types-exports', rule, { type C = boolean; type D = symbol; - declare const a: [A, B] | ([Array, Set] & number); + declare const a: [A, B] | ([Array, Set] & Exclude); export const value = { a }; `, From 15fc51cf52d88b2f9954ad34587547fcc8c4b547 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Fri, 28 Jun 2024 18:54:26 +0300 Subject: [PATCH 40/86] style --- packages/eslint-plugin/src/rules/require-types-exports.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 8cf70d1a13f5..c1e618373a86 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -235,6 +235,7 @@ function getTypeReferencesRecursively( collect(nodeToCheck); }); + break; case AST_NODE_TYPES.ArrowFunctionExpression: @@ -246,6 +247,7 @@ function getTypeReferencesRecursively( scope.through.forEach(ref => { collect(ref.identifier.parent); }); + break; } @@ -284,18 +286,17 @@ function getTypeReferencesRecursively( if (node.typeAnnotation?.typeAnnotation) { collect(node.typeAnnotation.typeAnnotation); } + break; case AST_NODE_TYPES.TSQualifiedName: collect(node.parent); break; - case AST_NODE_TYPES.TSAsExpression: { + case AST_NODE_TYPES.TSAsExpression: collect(node.expression); collect(node.typeAnnotation); - break; - } default: break; From 59eda588f37e2615b05e274e3756e0b6a38166f1 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sat, 29 Jun 2024 22:07:19 +0300 Subject: [PATCH 41/86] wip --- .../src/rules/require-types-exports.ts | 6 +++ .../tests/rules/require-types-exports.test.ts | 51 ++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index c1e618373a86..259d8294436e 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -238,6 +238,12 @@ function getTypeReferencesRecursively( break; + case AST_NODE_TYPES.NewExpression: + collect(node.callee); + node.arguments.forEach(arg => collect(arg)); + node.typeArguments?.params.forEach(param => collect(param)); + break; + case AST_NODE_TYPES.ArrowFunctionExpression: case AST_NODE_TYPES.FunctionDeclaration: case AST_NODE_TYPES.FunctionExpression: diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index c80d0ee13039..44d0a9fe4dd4 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -416,29 +416,29 @@ ruleTester.run('require-types-exports', rule, { ` 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, @@ -2647,5 +2647,44 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + type Item = { + key: string; + value: number; + }; + + type ItemKey = Item['key']; + + const item: Item = { key: 'apple', value: 1 }; + + const map = new Map([['apple', item]]); + + export const value = { + map, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 9, + column: 21, + endColumn: 25, + data: { + name: 'Item', + }, + }, + { + messageId: 'requireTypeExport', + line: 11, + column: 29, + endColumn: 36, + data: { + name: 'ItemKey', + }, + }, + ], + }, ], }); From fc0858a37e44812df7ce7c5446e2e3e5cdf82c61 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sat, 29 Jun 2024 22:41:38 +0300 Subject: [PATCH 42/86] wip --- .../src/rules/require-types-exports.ts | 44 +++--- .../tests/rules/require-types-exports.test.ts | 125 ++++++++++++++++++ 2 files changed, 154 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 259d8294436e..19937a0a3901 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -173,10 +173,17 @@ function getTypeReferencesRecursively( sourceCode: TSESLint.SourceCode, ): Set { const typeReferences = new Set(); + const visited = new Set(); collect(node); function collect(node: TSESTree.Node): void { + if (visited.has(node)) { + return; + } + + visited.add(node); + switch (node.type) { case AST_NODE_TYPES.VariableDeclarator: collect(node.id); @@ -193,20 +200,14 @@ function getTypeReferencesRecursively( collect(typeAnnotation); } - // If it's a reference to a variable inside an object or an array, - // we need to get the declared variable. - if ( - node.parent.type === AST_NODE_TYPES.Property || - node.parent.type === AST_NODE_TYPES.ArrayExpression - ) { - const scope = sourceCode.getScope(node); - const variableNode = getVariable(node.name, scope); - - variableNode?.defs.forEach(def => { - collect(def.name); - collect(def.node); - }); - } + // Resolve the variable to its declaration (in cases where the variable is referenced) + const scope = sourceCode.getScope(node); + const variableNode = getVariable(node.name, scope); + + variableNode?.defs.forEach(def => { + collect(def.name); + collect(def.node); + }); break; } @@ -239,6 +240,7 @@ function getTypeReferencesRecursively( break; case AST_NODE_TYPES.NewExpression: + case AST_NODE_TYPES.CallExpression: collect(node.callee); node.arguments.forEach(arg => collect(arg)); node.typeArguments?.params.forEach(param => collect(param)); @@ -251,12 +253,24 @@ function getTypeReferencesRecursively( const scope = sourceCode.getScope(node); scope.through.forEach(ref => { - collect(ref.identifier.parent); + const isSelfReference = ref.identifier.parent === node; + + const nodeToCheck = isSelfReference + ? ref.identifier + : ref.identifier.parent; + + collect(nodeToCheck); }); break; } + case AST_NODE_TYPES.ReturnStatement: + if (node.argument) { + collect(node.argument); + } + break; + case AST_NODE_TYPES.TSTypeReference: { const scope = sourceCode.getScope(node); const variable = getVariable(getTypeName(node.typeName), scope); diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 44d0a9fe4dd4..b613d38f8ecb 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -2648,6 +2648,62 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + key: () => a, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 26, + endColumn: 37, + data: { + name: 'Fruit', + }, + }, + ], + }, + + { + code: ` + enum Fruit { + Apple, + Banana, + Cherry, + } + + declare const a: Fruit.Apple; + + export const value = { + key: function () { + return a; + }, + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 26, + endColumn: 37, + data: { + name: 'Fruit', + }, + }, + ], + }, + { code: ` type Item = { @@ -2686,5 +2742,74 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: (() => item)(), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 21, + endColumn: 22, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => a)(item), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 21, + endColumn: 22, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: T) => a)(item), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 28, + endColumn: 29, + data: { + name: 'A', + }, + }, + ], + }, ], }); From 94a98ebc95f752dbac8b64b25650cc2670e40388 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sat, 29 Jun 2024 23:07:16 +0300 Subject: [PATCH 43/86] configs --- packages/eslint-plugin/src/configs/disable-type-checked.ts | 1 - packages/eslint-plugin/src/configs/strict-type-checked-only.ts | 1 - packages/eslint-plugin/src/configs/strict.ts | 1 + packages/typescript-eslint/src/configs/disable-type-checked.ts | 1 - .../typescript-eslint/src/configs/strict-type-checked-only.ts | 1 - packages/typescript-eslint/src/configs/strict.ts | 1 + 6 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index 3e5502db8479..8e47f9688d73 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -58,7 +58,6 @@ export = { '@typescript-eslint/promise-function-async': 'off', '@typescript-eslint/require-array-sort-compare': 'off', '@typescript-eslint/require-await': 'off', - '@typescript-eslint/require-types-exports': 'off', '@typescript-eslint/restrict-plus-operands': 'off', '@typescript-eslint/restrict-template-expressions': 'off', '@typescript-eslint/return-await': '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 5330f50018a3..a17bc16c257e 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -43,7 +43,6 @@ export = { '@typescript-eslint/prefer-return-this-type': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': [ 'error', { diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index 9c51d5c47348..cb825c95d3cb 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -40,6 +40,7 @@ export = { '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unified-signatures': 'error', }, diff --git a/packages/typescript-eslint/src/configs/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts index 3dd4b859a2bf..3018fcabd9a8 100644 --- a/packages/typescript-eslint/src/configs/disable-type-checked.ts +++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts @@ -61,7 +61,6 @@ export default ( '@typescript-eslint/promise-function-async': 'off', '@typescript-eslint/require-array-sort-compare': 'off', '@typescript-eslint/require-await': 'off', - '@typescript-eslint/require-types-exports': 'off', '@typescript-eslint/restrict-plus-operands': 'off', '@typescript-eslint/restrict-template-expressions': 'off', '@typescript-eslint/return-await': 'off', 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 47c853df2b98..cd45ade64d2f 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts @@ -52,7 +52,6 @@ export default ( '@typescript-eslint/prefer-return-this-type': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', - '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/restrict-plus-operands': [ 'error', { diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index 71615896c4dd..f208b0a4a286 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -49,6 +49,7 @@ export default ( '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + '@typescript-eslint/require-types-exports': 'error', '@typescript-eslint/triple-slash-reference': 'error', '@typescript-eslint/unified-signatures': 'error', }, From dee0fe460906564c82415d7a7a1a2708311b6ad2 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sat, 29 Jun 2024 23:21:29 +0300 Subject: [PATCH 44/86] don't report generic params in call expression --- .../src/rules/require-types-exports.ts | 9 +++++++- .../tests/rules/require-types-exports.test.ts | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 19937a0a3901..bf376cbd00b4 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,6 +1,7 @@ import { DefinitionType, ImplicitLibVariable, + ScopeType, } from '@typescript-eslint/scope-manager'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; @@ -277,7 +278,13 @@ function getTypeReferencesRecursively( const isBuiltinType = variable instanceof ImplicitLibVariable; - if (!isBuiltinType) { + const isGenericTypeArg = + variable?.scope.type === ScopeType.function && + variable.identifiers.every( + id => id.parent.type === AST_NODE_TYPES.TSTypeParameter, + ); + + if (!isBuiltinType && !isGenericTypeArg) { typeReferences.add(node); } diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index b613d38f8ecb..dced7772d9b6 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -2811,5 +2811,28 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + type A = number; + + export function func1(arg: R): R { + return func2(arg); + } + + declare function func2(arg: T): T; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, ], }); From 0804b241d77c47e8fe66ed09e90ea0d14983e8dd Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 08:27:25 +0300 Subject: [PATCH 45/86] improve function types collection --- .../src/rules/require-types-exports.ts | 83 +++++++++++------- .../tests/rules/require-types-exports.test.ts | 85 +++++++++++++++++++ 2 files changed, 137 insertions(+), 31 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index bf376cbd00b4..d2da69bd844f 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -178,7 +178,11 @@ function getTypeReferencesRecursively( collect(node); - function collect(node: TSESTree.Node): void { + function collect(node: TSESTree.Node | null | undefined): void { + if (!node) { + return; + } + if (visited.has(node)) { return; } @@ -195,11 +199,7 @@ function getTypeReferencesRecursively( break; case AST_NODE_TYPES.Identifier: { - const typeAnnotation = node.typeAnnotation?.typeAnnotation; - - if (typeAnnotation) { - collect(typeAnnotation); - } + collect(node.typeAnnotation?.typeAnnotation); // Resolve the variable to its declaration (in cases where the variable is referenced) const scope = sourceCode.getScope(node); @@ -226,12 +226,8 @@ function getTypeReferencesRecursively( case AST_NODE_TYPES.ArrayExpression: node.elements.forEach(element => { - if (!element) { - return; - } - const nodeToCheck = - element.type === AST_NODE_TYPES.SpreadElement + element?.type === AST_NODE_TYPES.SpreadElement ? element.argument : element; @@ -250,26 +246,53 @@ function getTypeReferencesRecursively( case AST_NODE_TYPES.ArrowFunctionExpression: case AST_NODE_TYPES.FunctionDeclaration: case AST_NODE_TYPES.FunctionExpression: - case AST_NODE_TYPES.TSDeclareFunction: { - const scope = sourceCode.getScope(node); - - scope.through.forEach(ref => { - const isSelfReference = ref.identifier.parent === node; + case AST_NODE_TYPES.TSDeclareFunction: + node.typeParameters?.params.forEach(param => collect(param.constraint)); + node.params.forEach(collect); + + /** + * If there is a return type annotation - collect the types from there. + * Otherwise - infer the return type from the return statements. + */ + if (node.returnType) { + collect(node.returnType.typeAnnotation); + } else { + collect(node.body); + } - const nodeToCheck = isSelfReference - ? ref.identifier - : ref.identifier.parent; + break; - collect(nodeToCheck); + case AST_NODE_TYPES.BlockStatement: + node.body.forEach(item => { + if (item.type === AST_NODE_TYPES.ReturnStatement) { + collect(item); + } }); + break; + + case AST_NODE_TYPES.AssignmentPattern: + collect(node.left); + break; + + case AST_NODE_TYPES.RestElement: + collect(node.argument); + collect(node.typeAnnotation?.typeAnnotation); + break; + + case AST_NODE_TYPES.ObjectPattern: + node.properties.forEach(collect); + collect(node.typeAnnotation?.typeAnnotation); + + break; + + case AST_NODE_TYPES.ArrayPattern: + node.elements.forEach(collect); + collect(node.typeAnnotation?.typeAnnotation); break; - } case AST_NODE_TYPES.ReturnStatement: - if (node.argument) { - collect(node.argument); - } + collect(node.argument); break; case AST_NODE_TYPES.TSTypeReference: { @@ -288,7 +311,7 @@ function getTypeReferencesRecursively( typeReferences.add(node); } - node.typeArguments?.params.forEach(param => collect(param)); + node.typeArguments?.params.forEach(collect); break; } @@ -297,22 +320,20 @@ function getTypeReferencesRecursively( break; case AST_NODE_TYPES.TSTupleType: - node.elementTypes.forEach(element => collect(element)); + node.elementTypes.forEach(collect); break; case AST_NODE_TYPES.TSUnionType: case AST_NODE_TYPES.TSIntersectionType: - node.types.forEach(type => collect(type)); + node.types.forEach(collect); break; case AST_NODE_TYPES.TSTypeLiteral: - node.members.forEach(member => collect(member)); + node.members.forEach(collect); break; case AST_NODE_TYPES.TSPropertySignature: - if (node.typeAnnotation?.typeAnnotation) { - collect(node.typeAnnotation.typeAnnotation); - } + collect(node.typeAnnotation?.typeAnnotation); break; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index dced7772d9b6..47ec6ba0c309 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -2834,5 +2834,90 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + type A = number; + type B = string; + + export function func1(arg: R): R { + doWork(String(arg)); + + return arg; + } + + declare function doWork(arg: B): void; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = number; + + export function func1(arg: R) { + return func2(arg); + } + + declare function func2(arg: B): B; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 9, + column: 37, + endColumn: 38, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + export function func1(arg: R): R { + function doWork(arg2: B): void {} + + doWork(String(arg)); + + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, ], }); From a0a4944e91a5b1c6022811e4c27099c28f2c702c Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 09:43:33 +0300 Subject: [PATCH 46/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index d2da69bd844f..2cd0c8434bde 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -192,10 +192,7 @@ function getTypeReferencesRecursively( switch (node.type) { case AST_NODE_TYPES.VariableDeclarator: collect(node.id); - - if (node.init) { - collect(node.init); - } + collect(node.init); break; case AST_NODE_TYPES.Identifier: { @@ -334,7 +331,6 @@ function getTypeReferencesRecursively( case AST_NODE_TYPES.TSPropertySignature: collect(node.typeAnnotation?.typeAnnotation); - break; case AST_NODE_TYPES.TSQualifiedName: From 66a0affc6cbdfe21ee8a9fdb5ee9499600c0b986 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 10:07:00 +0300 Subject: [PATCH 47/86] wip --- .../src/rules/require-types-exports.ts | 5 +++ .../tests/rules/require-types-exports.test.ts | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 2cd0c8434bde..2d54c39fb32d 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -342,6 +342,11 @@ function getTypeReferencesRecursively( collect(node.typeAnnotation); break; + case AST_NODE_TYPES.TSIndexedAccessType: + collect(node.objectType); + collect(node.indexType); + break; + default: break; } diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 47ec6ba0c309..d8385868a6d1 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -2919,5 +2919,36 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + type ItemsMap = Record; + type Key = keyof ItemsMap; + + export function get(key: K): ItemsMap[K] { + return key as never; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 42, + data: { + name: 'Key', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 53, + endColumn: 61, + data: { + name: 'ItemsMap', + }, + }, + ], + }, ], }); From b67e1f990f60ab23be175b7459d533f2938cd9cc Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 14:22:35 +0300 Subject: [PATCH 48/86] remove `getVariable` --- packages/eslint-plugin/src/rules/require-types-exports.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 2d54c39fb32d..69bc558c6857 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -6,7 +6,7 @@ import { import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { createRule, findVariable } from '../util'; type MessageIds = 'requireTypeExport'; @@ -74,7 +74,7 @@ export default createRule<[], MessageIds>({ }, ): void { const scope = context.sourceCode.getScope(node); - const variable = getVariable(node.declaration.name, scope); + const variable = findVariable(scope, node.declaration.name); if (!variable) { return; @@ -200,7 +200,7 @@ function getTypeReferencesRecursively( // Resolve the variable to its declaration (in cases where the variable is referenced) const scope = sourceCode.getScope(node); - const variableNode = getVariable(node.name, scope); + const variableNode = findVariable(scope, node.name); variableNode?.defs.forEach(def => { collect(def.name); @@ -294,7 +294,7 @@ function getTypeReferencesRecursively( case AST_NODE_TYPES.TSTypeReference: { const scope = sourceCode.getScope(node); - const variable = getVariable(getTypeName(node.typeName), scope); + const variable = findVariable(scope, getTypeName(node.typeName)); const isBuiltinType = variable instanceof ImplicitLibVariable; From 9891e78d2c2c71fe6011ef6741c04780843d01db Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 16:33:43 +0300 Subject: [PATCH 49/86] infer return type from return statements --- .../src/rules/require-types-exports.ts | 53 +++++++++---- .../tests/rules/require-types-exports.test.ts | 76 +++++++++++++++++++ packages/utils/src/ts-estree.ts | 2 + 3 files changed, 116 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 69bc558c6857..2339df950c65 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -4,7 +4,7 @@ import { ScopeType, } from '@typescript-eslint/scope-manager'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, simpleTraverse } from '@typescript-eslint/utils'; import { createRule, findVariable } from '../util'; @@ -254,7 +254,7 @@ function getTypeReferencesRecursively( if (node.returnType) { collect(node.returnType.typeAnnotation); } else { - collect(node.body); + collectFunctionReturnStatements(node).forEach(collect); } break; @@ -355,22 +355,45 @@ function getTypeReferencesRecursively( return typeReferences; } -function getVariable( - name: string, - initialScope: TSESLint.Scope.Scope | null, -): TSESLint.Scope.Variable | null { - let variable: TSESLint.Scope.Variable | null = null; - let scope: TSESLint.Scope.Scope | null = initialScope; +function collectFunctionReturnStatements( + functionNode: TSESTree.Node, +): Set { + const isArrowFunctionReturn = + functionNode.type === AST_NODE_TYPES.ArrowFunctionExpression && + functionNode.body.type === AST_NODE_TYPES.Identifier; - while (scope) { - variable = scope.set.get(name) ?? null; + if (isArrowFunctionReturn) { + return new Set([functionNode.body]); + } - if (variable) { - break; - } + const returnStatements = new Set(); + + simpleTraverse(functionNode, { + visitors: { + ReturnStatement: (node: TSESTree.Node) => { + if (getParentFunction(node) === functionNode) { + returnStatements.add(node); + } + }, + }, + }); + + return returnStatements; +} + +function getParentFunction(node: TSESTree.Node): TSESTree.Node | null { + let parent: TSESTree.Node | undefined = node.parent; + + const functionTypes = new Set([ + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.TSDeclareFunction, + ]); - scope = scope.upper; + while (parent && !functionTypes.has(parent.type)) { + parent = parent.parent; } - return variable; + return parent ?? null; } diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index d8385868a6d1..26f0727da618 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -2894,6 +2894,82 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type A = number; + type B = number; + type C = number; + + export function func1(arg: R) { + if (Math.random() > 0.5) { + return func2(arg); + } else { + return func3(arg); + } + } + + declare function func2(arg: B): B; + declare function func3(arg: C): C; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 14, + column: 37, + endColumn: 38, + data: { + name: 'B', + }, + }, + { + messageId: 'requireTypeExport', + line: 15, + column: 37, + endColumn: 38, + data: { + name: 'C', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = number; + + export function func1(arg: R) { + const a = (() => { + return func2(arg); + })(); + + return arg; + } + + declare function func2(arg: B): B; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + { code: ` type A = number; diff --git a/packages/utils/src/ts-estree.ts b/packages/utils/src/ts-estree.ts index 212d339c4ba3..fb6f5b34eb6e 100644 --- a/packages/utils/src/ts-estree.ts +++ b/packages/utils/src/ts-estree.ts @@ -12,3 +12,5 @@ export type { ParserServicesWithTypeInformation, ParserServicesWithoutTypeInformation, } from '@typescript-eslint/typescript-estree'; + +export { simpleTraverse } from '@typescript-eslint/typescript-estree'; From cb90d4305b276d72d4e86f29d79155efaa97161f Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 16:42:23 +0300 Subject: [PATCH 50/86] wip --- .../src/rules/require-types-exports.ts | 7 ++--- .../tests/rules/require-types-exports.test.ts | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 2339df950c65..95987dde4250 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -251,11 +251,8 @@ function getTypeReferencesRecursively( * If there is a return type annotation - collect the types from there. * Otherwise - infer the return type from the return statements. */ - if (node.returnType) { - collect(node.returnType.typeAnnotation); - } else { - collectFunctionReturnStatements(node).forEach(collect); - } + collect(node.returnType?.typeAnnotation); + collectFunctionReturnStatements(node).forEach(collect); break; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 26f0727da618..2acbbda6957b 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -2970,6 +2970,37 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type A = number; + type B = number; + + export function func1(arg: R) { + return arg as B; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 41, + endColumn: 42, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 25, + endColumn: 26, + data: { + name: 'B', + }, + }, + ], + }, + { code: ` type A = number; From 479f593c23d30040b5aeb2c9ba4cbd4f95e4ff2b Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 17:44:40 +0300 Subject: [PATCH 51/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 95987dde4250..4e7d800c1093 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -246,11 +246,6 @@ function getTypeReferencesRecursively( case AST_NODE_TYPES.TSDeclareFunction: node.typeParameters?.params.forEach(param => collect(param.constraint)); node.params.forEach(collect); - - /** - * If there is a return type annotation - collect the types from there. - * Otherwise - infer the return type from the return statements. - */ collect(node.returnType?.typeAnnotation); collectFunctionReturnStatements(node).forEach(collect); From e86427fab8f3487eeec9a671ca606c47c97c0cf1 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 18:00:33 +0300 Subject: [PATCH 52/86] wip --- .../src/rules/require-types-exports.ts | 11 ++ .../tests/rules/require-types-exports.test.ts | 101 ++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 4e7d800c1093..bc482d3a84ab 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -240,6 +240,17 @@ function getTypeReferencesRecursively( node.typeArguments?.params.forEach(param => collect(param)); break; + case AST_NODE_TYPES.BinaryExpression: + case AST_NODE_TYPES.LogicalExpression: + collect(node.left); + collect(node.right); + break; + + case AST_NODE_TYPES.ConditionalExpression: + collect(node.consequent); + collect(node.alternate); + break; + case AST_NODE_TYPES.ArrowFunctionExpression: case AST_NODE_TYPES.FunctionDeclaration: case AST_NODE_TYPES.FunctionExpression: diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 2acbbda6957b..c6cd866dbff3 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -3057,5 +3057,106 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + skip: true, + only: true, + code: ` + type A = number; + type B = string; + type C = boolean; + + const obj: { key: { to: A; and: C } } = { + key: { + to: 1, + and: true, + }, + }; + + const obj2 = { + b: 'asd' as B, + }; + + export function func() { + if (Math.random() > 0.5) { + return obj.key; + } + + if (Math.random() < 0.5) { + return obj.key.to; + } + + return obj.key.to + obj2.b + 'asd'; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 42, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const value: A = 1; + + export function func() { + return Math.random() > 0.5 && value; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 22, + endColumn: 23, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + type B = string; + + const valueA: A = 1; + const valueB: B = 'test'; + + export function func() { + return Math.random() > 0.5 ? valueA : valueB; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 23, + endColumn: 24, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 6, + column: 23, + endColumn: 24, + data: { + name: 'B', + }, + }, + ], + }, ], }); From a61d49fdca423c63171a4b9e4e132544cf2dda07 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 18:36:57 +0300 Subject: [PATCH 53/86] wip --- .../tests/rules/require-types-exports.test.ts | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index c6cd866dbff3..d63a45a9f432 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -3058,50 +3058,6 @@ ruleTester.run('require-types-exports', rule, { ], }, - { - skip: true, - only: true, - code: ` - type A = number; - type B = string; - type C = boolean; - - const obj: { key: { to: A; and: C } } = { - key: { - to: 1, - and: true, - }, - }; - - const obj2 = { - b: 'asd' as B, - }; - - export function func() { - if (Math.random() > 0.5) { - return obj.key; - } - - if (Math.random() < 0.5) { - return obj.key.to; - } - - return obj.key.to + obj2.b + 'asd'; - } - `, - errors: [ - { - messageId: 'requireTypeExport', - line: 5, - column: 39, - endColumn: 42, - data: { - name: 'A', - }, - }, - ], - }, - { code: ` type A = number; From 121f47572a0183867a4fff096c23d977048fc6c1 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 18:37:31 +0300 Subject: [PATCH 54/86] wip --- packages/eslint-plugin/src/rules/require-types-exports.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index bc482d3a84ab..da478b78e8f7 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -179,11 +179,7 @@ function getTypeReferencesRecursively( collect(node); function collect(node: TSESTree.Node | null | undefined): void { - if (!node) { - return; - } - - if (visited.has(node)) { + if (!node || visited.has(node)) { return; } From f3f8518b80fc2ce3f2ee11be9037f1be7c666c4b Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 30 Jun 2024 18:45:33 +0300 Subject: [PATCH 55/86] wip --- .../eslint-plugin/src/rules/require-types-exports.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index da478b78e8f7..c3c0f3c7be1f 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -202,7 +202,6 @@ function getTypeReferencesRecursively( collect(def.name); collect(def.node); }); - break; } @@ -226,7 +225,6 @@ function getTypeReferencesRecursively( collect(nodeToCheck); }); - break; case AST_NODE_TYPES.NewExpression: @@ -255,15 +253,6 @@ function getTypeReferencesRecursively( node.params.forEach(collect); collect(node.returnType?.typeAnnotation); collectFunctionReturnStatements(node).forEach(collect); - - break; - - case AST_NODE_TYPES.BlockStatement: - node.body.forEach(item => { - if (item.type === AST_NODE_TYPES.ReturnStatement) { - collect(item); - } - }); break; case AST_NODE_TYPES.AssignmentPattern: @@ -278,7 +267,6 @@ function getTypeReferencesRecursively( case AST_NODE_TYPES.ObjectPattern: node.properties.forEach(collect); collect(node.typeAnnotation?.typeAnnotation); - break; case AST_NODE_TYPES.ArrayPattern: From ab837b455e45207d06c7ec72fe14192660dcea40 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 20:01:15 +0300 Subject: [PATCH 56/86] custom traversal --- .../src/rules/require-types-exports.ts | 84 +++++++++++++------ packages/utils/src/ts-estree.ts | 2 - 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index c3c0f3c7be1f..9de97daca013 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -4,7 +4,7 @@ import { ScopeType, } from '@typescript-eslint/scope-manager'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, simpleTraverse } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, findVariable } from '../util'; @@ -252,7 +252,10 @@ function getTypeReferencesRecursively( node.typeParameters?.params.forEach(param => collect(param.constraint)); node.params.forEach(collect); collect(node.returnType?.typeAnnotation); - collectFunctionReturnStatements(node).forEach(collect); + + if (node.body) { + collectFunctionReturnStatements(node).forEach(collect); + } break; case AST_NODE_TYPES.AssignmentPattern: @@ -343,11 +346,14 @@ function getTypeReferencesRecursively( } function collectFunctionReturnStatements( - functionNode: TSESTree.Node, + functionNode: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression, ): Set { const isArrowFunctionReturn = functionNode.type === AST_NODE_TYPES.ArrowFunctionExpression && - functionNode.body.type === AST_NODE_TYPES.Identifier; + functionNode.body.type !== AST_NODE_TYPES.BlockStatement; if (isArrowFunctionReturn) { return new Set([functionNode.body]); @@ -355,32 +361,58 @@ function collectFunctionReturnStatements( const returnStatements = new Set(); - simpleTraverse(functionNode, { - visitors: { - ReturnStatement: (node: TSESTree.Node) => { - if (getParentFunction(node) === functionNode) { - returnStatements.add(node); - } - }, - }, - }); + forEachReturnStatement(functionNode, returnNode => + returnStatements.add(returnNode), + ); return returnStatements; } -function getParentFunction(node: TSESTree.Node): TSESTree.Node | null { - let parent: TSESTree.Node | undefined = node.parent; - - const functionTypes = new Set([ - AST_NODE_TYPES.ArrowFunctionExpression, - AST_NODE_TYPES.FunctionDeclaration, - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.TSDeclareFunction, - ]); +// Heavily inspired by: +// https://github.com/typescript-eslint/typescript-eslint/blob/103de6eed/packages/eslint-plugin/src/util/astUtils.ts#L47-L80 +export 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; - while (parent && !functionTypes.has(parent.type)) { - parent = parent.parent; + case AST_NODE_TYPES.TryStatement: + traverse(node.block); + traverse(node.handler); + traverse(node.finalizer); + return; + } } - - return parent ?? null; } diff --git a/packages/utils/src/ts-estree.ts b/packages/utils/src/ts-estree.ts index fb6f5b34eb6e..212d339c4ba3 100644 --- a/packages/utils/src/ts-estree.ts +++ b/packages/utils/src/ts-estree.ts @@ -12,5 +12,3 @@ export type { ParserServicesWithTypeInformation, ParserServicesWithoutTypeInformation, } from '@typescript-eslint/typescript-estree'; - -export { simpleTraverse } from '@typescript-eslint/typescript-estree'; From 1641272bffa5c324091991cdce2cdaaa47fba592 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 20:06:52 +0300 Subject: [PATCH 57/86] some tests --- .../tests/rules/require-types-exports.test.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index d63a45a9f432..095d93f378e8 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -2812,6 +2812,52 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => [a])(item), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 21, + endColumn: 22, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + const item: A = 1; + + export const value = { + key: ((a: A) => ({ a }))(item), + }; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 7, + column: 21, + endColumn: 22, + data: { + name: 'A', + }, + }, + ], + }, + { code: ` type A = number; From fd56a1c7cd14f8ccb44b4173730de8509d7dbac3 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 20:37:28 +0300 Subject: [PATCH 58/86] add missing tests --- .../tests/rules/require-types-exports.test.ts | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 095d93f378e8..5c410b35162a 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -1906,6 +1906,113 @@ ruleTester.run('require-types-exports', rule, { ], }, + { + code: ` + namespace A { + export type B = number; + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 8, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + namespace A { + export interface B { + value: number; + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 10, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + namespace A { + export enum B { + Value1, + Value2, + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 11, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + namespace A { + export namespace B { + export type C = number; + } + } + + type B = string; + + export function a(arg: B) { + return arg; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 10, + column: 32, + endColumn: 33, + data: { + name: 'B', + }, + }, + ], + }, + { code: ` import type { A } from './types'; From b0613d5cdad57ae64ac902f6181d2eadb0d9def7 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 20:43:28 +0300 Subject: [PATCH 59/86] report default exported call expression --- .../src/rules/require-types-exports.ts | 25 +++---------------- .../tests/fixtures/tsconfig-with-dom.json | 9 +++++++ .../tests/rules/require-types-exports.test.ts | 23 ++++++++++++++++- 3 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 9de97daca013..80b53eaa6612 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,5 +1,4 @@ import { - DefinitionType, ImplicitLibVariable, ScopeType, } from '@typescript-eslint/scope-manager'; @@ -68,25 +67,10 @@ export default createRule<[], MessageIds>({ } } - function visitDefaultExportedIdentifier( - node: TSESTree.DefaultExportDeclarations & { - declaration: TSESTree.Identifier; - }, + function visitExportDefaultDeclaration( + node: TSESTree.ExportDefaultDeclaration, ): void { - const scope = context.sourceCode.getScope(node); - const variable = findVariable(scope, node.declaration.name); - - if (!variable) { - return; - } - - for (const def of variable.defs) { - if (def.type !== DefinitionType.Variable || !def.node.init) { - continue; - } - - checkNodeTypes(def.node); - } + checkNodeTypes(node.declaration); } function checkNodeTypes(node: TSESTree.Node): void { @@ -148,8 +132,7 @@ export default createRule<[], MessageIds>({ 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': visitExportedVariableDeclaration, - 'ExportDefaultDeclaration[declaration.type="Identifier"]': - visitDefaultExportedIdentifier, + ExportDefaultDeclaration: visitExportDefaultDeclaration, }; }, }); 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..87cfb2c4b422 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "ESNext", + "strict": true, + "esModuleInterop": true, + "lib": ["esnext", "DOM"] + } +} diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 5c410b35162a..e3bf4f43cf89 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -9,7 +9,7 @@ const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { tsconfigRootDir: rootPath, - project: './tsconfig.json', + project: './tsconfig-with-dom.json', }, }); @@ -3267,5 +3267,26 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + declare function func(): string; + + type A = string; + + export default func(); + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 6, + column: 29, + endColumn: 30, + data: { + name: 'A', + }, + }, + ], + }, ], }); From b9f11483cde5c3f61d3073fabb2c5a2bf1454e03 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 22:40:32 +0300 Subject: [PATCH 60/86] report types used within exported types --- .../src/rules/require-types-exports.ts | 36 ++++++ .../tests/rules/require-types-exports.test.ts | 103 ++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 80b53eaa6612..71414d6c5fcf 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -67,6 +67,22 @@ export default createRule<[], MessageIds>({ } } + function visitExportedTypeAliasDeclaration( + node: TSESTree.ExportNamedDeclaration & { + declaration: TSESTree.TSTypeAliasDeclaration; + }, + ): void { + checkNodeTypes(node.declaration.typeAnnotation); + } + + function visitExportedInterfaceDeclaration( + node: TSESTree.ExportNamedDeclaration & { + declaration: TSESTree.TSInterfaceDeclaration; + }, + ): void { + checkNodeTypes(node.declaration.body); + } + function visitExportDefaultDeclaration( node: TSESTree.ExportDefaultDeclaration, ): void { @@ -132,6 +148,18 @@ export default createRule<[], MessageIds>({ 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': visitExportedVariableDeclaration, + 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"]': + visitExportedTypeAliasDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedTypeAliasDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedInterfaceDeclaration, + + 'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': + visitExportedInterfaceDeclaration, + ExportDefaultDeclaration: visitExportDefaultDeclaration, }; }, @@ -302,6 +330,14 @@ function getTypeReferencesRecursively( node.members.forEach(collect); break; + case AST_NODE_TYPES.TSTemplateLiteralType: + node.types.forEach(collect); + break; + + case AST_NODE_TYPES.TSInterfaceBody: + node.body.forEach(collect); + break; + case AST_NODE_TYPES.TSPropertySignature: collect(node.typeAnnotation?.typeAnnotation); break; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index e3bf4f43cf89..b0dde505720b 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -3288,5 +3288,108 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + type Apple = 'apple'; + type Banana = 'banana'; + + export type Fruites = Apple | Banana; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 31, + endColumn: 36, + data: { + name: 'Apple', + }, + }, + { + messageId: 'requireTypeExport', + line: 5, + column: 39, + endColumn: 45, + data: { + name: 'Banana', + }, + }, + ], + }, + + { + code: ` + type A = number; + + export interface B { + a: A; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 14, + endColumn: 15, + data: { + name: 'A', + }, + }, + ], + }, + + { + code: ` + type A = number; + + interface B { + b: string; + } + + export namespace C { + export type D = A; + export type E = B; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 9, + column: 27, + endColumn: 28, + data: { + name: 'A', + }, + }, + { + messageId: 'requireTypeExport', + line: 10, + column: 27, + endColumn: 28, + data: { + name: 'B', + }, + }, + ], + }, + + { + code: ` + type A = 'test'; + export type B = \`test-\${A}\`; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 3, + column: 33, + endColumn: 34, + data: { + name: 'A', + }, + }, + ], + }, ], }); From 0415b604b6f2cb5f79d260ba86e33855faaf80ca Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 23:02:33 +0300 Subject: [PATCH 61/86] fix false positives due to ordering --- .../src/rules/require-types-exports.ts | 38 ++++++++++++------- .../tests/rules/require-types-exports.test.ts | 12 ++++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 71414d6c5fcf..e17cb7102923 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -36,13 +36,32 @@ export default createRule<[], MessageIds>({ externalizedTypes.add(node.local.name); } - function collectExportedTypes( - node: + function collectExportedTypes(node: TSESTree.Program): void { + const isCollectableType = ( + node: TSESTree.Node, + ): node is | TSESTree.TSTypeAliasDeclaration | TSESTree.TSInterfaceDeclaration - | TSESTree.TSEnumDeclaration, - ): void { - externalizedTypes.add(node.id.name); + | TSESTree.TSEnumDeclaration + | TSESTree.TSModuleDeclaration => { + return [ + AST_NODE_TYPES.TSTypeAliasDeclaration, + AST_NODE_TYPES.TSInterfaceDeclaration, + AST_NODE_TYPES.TSEnumDeclaration, + AST_NODE_TYPES.TSModuleDeclaration, + ].includes(node.type); + }; + + node.body.forEach(statement => { + if ( + statement.type === AST_NODE_TYPES.ExportNamedDeclaration && + statement.declaration && + isCollectableType(statement.declaration) && + statement.declaration.id.type === AST_NODE_TYPES.Identifier + ) { + externalizedTypes.add(statement.declaration.id.name); + } + }); } function visitExportedFunctionDeclaration( @@ -124,14 +143,7 @@ export default createRule<[], MessageIds>({ 'ImportDeclaration ImportNamespaceSpecifier': collectImportedTypes, 'ImportDeclaration ImportDefaultSpecifier': collectImportedTypes, - 'Program > ExportNamedDeclaration > TSTypeAliasDeclaration': - collectExportedTypes, - 'Program > ExportNamedDeclaration > TSInterfaceDeclaration': - collectExportedTypes, - 'Program > ExportNamedDeclaration > TSEnumDeclaration': - collectExportedTypes, - 'Program > ExportNamedDeclaration > TSModuleDeclaration': - collectExportedTypes, + Program: collectExportedTypes, 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': visitExportedFunctionDeclaration, diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index b0dde505720b..a465f1fb6b42 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -444,6 +444,18 @@ ruleTester.run('require-types-exports', rule, { func, }; `, + + ` + export function func1() { + return func2(1); + } + + export type A = number; + + export function func2(arg: A) { + return 1; + } + `, ], invalid: [ From a0c236ed38de7a4ea708b54d8723648928147837 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 23:27:11 +0300 Subject: [PATCH 62/86] change message --- packages/eslint-plugin/src/rules/require-types-exports.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index e17cb7102923..ce18c84b698d 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -18,7 +18,8 @@ export default createRule<[], MessageIds>({ recommended: 'strict', }, messages: { - requireTypeExport: 'Expected type "{{ name }}" to be exported', + requireTypeExport: + '"{{ name }}" is used in other exports from this file, so it should also be exported.', }, schema: [], }, From 3d5d695ac54ddcb0cbde86affb3a3ef369b32448 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 23:27:57 +0300 Subject: [PATCH 63/86] wip --- .../src/rules/require-types-exports.ts | 39 +++++++++---------- .../tests/rules/require-types-exports.test.ts | 2 + 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index ce18c84b698d..e9315938ef9b 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -7,7 +7,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, findVariable } from '../util'; -type MessageIds = 'requireTypeExport'; +export type MessageIds = 'requireTypeExport'; export default createRule<[], MessageIds>({ name: 'require-types-exports', @@ -87,20 +87,14 @@ export default createRule<[], MessageIds>({ } } - function visitExportedTypeAliasDeclaration( + function visitExportedTypeDeclaration( node: TSESTree.ExportNamedDeclaration & { - declaration: TSESTree.TSTypeAliasDeclaration; + declaration: + | TSESTree.TSTypeAliasDeclaration + | TSESTree.TSInterfaceDeclaration; }, ): void { - checkNodeTypes(node.declaration.typeAnnotation); - } - - function visitExportedInterfaceDeclaration( - node: TSESTree.ExportNamedDeclaration & { - declaration: TSESTree.TSInterfaceDeclaration; - }, - ): void { - checkNodeTypes(node.declaration.body); + checkNodeTypes(node.declaration); } function visitExportDefaultDeclaration( @@ -162,16 +156,16 @@ export default createRule<[], MessageIds>({ visitExportedVariableDeclaration, 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"]': - visitExportedTypeAliasDeclaration, + visitExportedTypeDeclaration, 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': - visitExportedTypeAliasDeclaration, + visitExportedTypeDeclaration, 'ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': - visitExportedInterfaceDeclaration, + visitExportedTypeDeclaration, 'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': - visitExportedInterfaceDeclaration, + visitExportedTypeDeclaration, ExportDefaultDeclaration: visitExportDefaultDeclaration, }; @@ -313,7 +307,8 @@ function getTypeReferencesRecursively( const isBuiltinType = variable instanceof ImplicitLibVariable; const isGenericTypeArg = - variable?.scope.type === ScopeType.function && + (variable?.scope.type === ScopeType.function || + variable?.scope.type === ScopeType.type) && variable.identifiers.every( id => id.parent.type === AST_NODE_TYPES.TSTypeParameter, ); @@ -347,8 +342,12 @@ function getTypeReferencesRecursively( node.types.forEach(collect); break; - case AST_NODE_TYPES.TSInterfaceBody: - node.body.forEach(collect); + case AST_NODE_TYPES.TSTypeAliasDeclaration: + collect(node.typeAnnotation); + break; + + case AST_NODE_TYPES.TSInterfaceDeclaration: + node.body.body.forEach(collect); break; case AST_NODE_TYPES.TSPropertySignature: @@ -402,7 +401,7 @@ function collectFunctionReturnStatements( // Heavily inspired by: // https://github.com/typescript-eslint/typescript-eslint/blob/103de6eed/packages/eslint-plugin/src/util/astUtils.ts#L47-L80 -export function forEachReturnStatement( +function forEachReturnStatement( functionNode: | TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index a465f1fb6b42..c5ddc08feb75 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -456,6 +456,8 @@ ruleTester.run('require-types-exports', rule, { return 1; } `, + + 'export type ValueOf = T[keyof T];', ], invalid: [ From 2e76ce6a83f73235ed5104177c8ab557eb21d28f Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 7 Jul 2024 23:49:09 +0300 Subject: [PATCH 64/86] fix some reports --- .../eslint-plugin/src/rules/array-type.ts | 4 +-- .../eslint-plugin/src/rules/ban-ts-comment.ts | 6 ++-- .../src/rules/class-literal-property-style.ts | 4 +-- .../src/rules/class-methods-use-this.ts | 4 +-- .../eslint-plugin/src/rules/comma-spacing.ts | 4 +-- .../rules/consistent-generic-constructors.ts | 4 +-- .../rules/consistent-indexed-object-style.ts | 4 +-- .../src/rules/consistent-return.ts | 6 ++-- .../src/rules/consistent-type-exports.ts | 4 +-- .../src/rules/consistent-type-imports.ts | 8 +++--- .../rules/explicit-function-return-type.ts | 4 +-- .../rules/explicit-member-accessibility.ts | 8 +++--- .../rules/explicit-module-boundary-types.ts | 4 +-- packages/eslint-plugin/src/rules/indent.ts | 4 +-- .../src/rules/lines-between-class-members.ts | 4 +-- .../src/rules/member-delimiter-style.ts | 12 ++++---- .../src/rules/member-ordering.ts | 28 +++++++++---------- .../src/rules/no-array-delete.ts | 2 +- .../src/rules/no-base-to-string.ts | 4 +-- .../src/rules/no-dupe-class-members.ts | 4 +-- .../src/rules/no-empty-function.ts | 4 +-- .../src/rules/no-empty-interface.ts | 4 +-- .../src/rules/no-extra-parens.ts | 4 +-- .../eslint-plugin/src/rules/no-extra-semi.ts | 4 +-- .../src/rules/no-extraneous-class.ts | 4 +-- .../src/rules/no-floating-promises.ts | 4 +-- .../src/rules/no-import-type-side-effects.ts | 4 +-- .../src/rules/no-inferrable-types.ts | 4 +-- .../src/rules/no-invalid-void-type.ts | 4 +-- .../eslint-plugin/src/rules/no-loop-func.ts | 4 +-- .../src/rules/no-loss-of-precision.ts | 6 ++-- .../src/rules/no-magic-numbers.ts | 4 +-- .../src/rules/no-meaningless-void-operator.ts | 2 +- .../src/rules/no-misused-promises.ts | 6 ++-- .../eslint-plugin/src/rules/no-namespace.ts | 4 +-- .../src/rules/no-non-null-assertion.ts | 2 +- .../eslint-plugin/src/rules/no-redeclare.ts | 7 +++-- .../src/rules/no-require-imports.ts | 4 +-- packages/eslint-plugin/src/rules/no-shadow.ts | 4 +-- .../eslint-plugin/src/rules/no-this-alias.ts | 4 +-- .../src/rules/no-throw-literal.ts | 4 +-- .../eslint-plugin/src/rules/no-type-alias.ts | 6 ++-- .../no-unnecessary-boolean-literal-compare.ts | 4 +-- .../no-unnecessary-template-expression.ts | 2 +- .../rules/no-unnecessary-type-arguments.ts | 2 +- .../rules/no-unnecessary-type-assertion.ts | 4 +-- .../src/rules/no-unsafe-argument.ts | 2 +- .../eslint-plugin/src/rules/no-unsafe-call.ts | 2 +- .../src/rules/no-unsafe-unary-minus.ts | 4 +-- .../src/rules/no-unused-expressions.ts | 4 +-- .../src/rules/no-use-before-define.ts | 6 ++-- .../src/rules/no-useless-constructor.ts | 4 +-- .../src/rules/no-useless-template-literals.ts | 2 +- .../src/rules/no-var-requires.ts | 4 +-- .../src/rules/only-throw-error.ts | 4 +-- .../rules/padding-line-between-statements.ts | 6 ++-- .../src/rules/parameter-properties.ts | 8 +++--- .../src/rules/prefer-destructuring.ts | 8 +++--- .../src/rules/prefer-enum-initializers.ts | 2 +- .../compareNodes.ts | 2 +- .../gatherLogicalOperands.ts | 2 +- .../rules/prefer-readonly-parameter-types.ts | 4 +-- .../src/rules/prefer-readonly.ts | 4 +-- .../rules/prefer-string-starts-ends-with.ts | 4 +-- .../src/rules/prefer-ts-expect-error.ts | 2 +- .../src/rules/promise-function-async.ts | 4 +-- .../src/rules/restrict-plus-operands.ts | 4 +-- .../rules/restrict-template-expressions.ts | 4 +-- .../src/rules/space-before-function-paren.ts | 4 +-- .../src/rules/switch-exhaustiveness-check.ts | 4 +-- .../src/rules/triple-slash-reference.ts | 4 +-- .../src/rules/type-annotation-spacing.ts | 4 +-- packages/eslint-plugin/src/rules/typedef.ts | 4 +-- .../eslint-plugin/src/rules/unbound-method.ts | 2 +- .../src/rules/unified-signatures.ts | 4 +-- .../use-unknown-in-catch-callback-variable.ts | 2 +- .../src/util/getESLintCoreRule.ts | 4 +-- .../src/util/getFunctionHeadLoc.ts | 2 +- .../src/util/getOperatorPrecedence.ts | 2 +- .../src/util/getWrappingFixer.ts | 2 +- .../cases/createTestCases.ts | 2 +- .../rules/prefer-optional-chain/base-cases.ts | 4 +-- 82 files changed, 181 insertions(+), 176 deletions(-) diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index 26b5c270914e..ebc32526ee89 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -72,13 +72,13 @@ function typeNeedsParentheses(node: TSESTree.Node): boolean { } export type OptionString = 'array-simple' | 'array' | 'generic'; -type Options = [ +export type Options = [ { default: OptionString; readonly?: OptionString; }, ]; -type MessageIds = +export type MessageIds = | 'errorStringArray' | 'errorStringArraySimple' | 'errorStringGeneric' diff --git a/packages/eslint-plugin/src/rules/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index b554510f57d3..71ec5696f867 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -3,12 +3,12 @@ import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule, getStringLength, nullThrows } from '../util'; -type DirectiveConfig = +export type DirectiveConfig = | boolean | 'allow-with-description' | { descriptionFormat: string }; -interface Options { +export interface Options { 'ts-expect-error'?: DirectiveConfig; 'ts-ignore'?: DirectiveConfig; 'ts-nocheck'?: DirectiveConfig; @@ -18,7 +18,7 @@ interface Options { const defaultMinimumDescriptionLength = 3; -type MessageIds = +export type MessageIds = | 'tsDirectiveComment' | 'tsIgnoreInsteadOfExpectError' | 'tsDirectiveCommentDescriptionNotMatchPattern' diff --git a/packages/eslint-plugin/src/rules/class-literal-property-style.ts b/packages/eslint-plugin/src/rules/class-literal-property-style.ts index 6813e80f60d8..d8aa8d124fe5 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -9,8 +9,8 @@ import { nullThrows, } from '../util'; -type Options = ['fields' | 'getters']; -type MessageIds = +export type Options = ['fields' | 'getters']; +export type MessageIds = | 'preferFieldStyle' | 'preferFieldStyleSuggestion' | 'preferGetterStyle' diff --git a/packages/eslint-plugin/src/rules/class-methods-use-this.ts b/packages/eslint-plugin/src/rules/class-methods-use-this.ts index 6236a46ddb7b..793c8e8a5d90 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -8,7 +8,7 @@ import { getStaticStringValue, } from '../util'; -type Options = [ +export type Options = [ { exceptMethods?: string[]; enforceForClassFields?: boolean; @@ -16,7 +16,7 @@ type Options = [ ignoreClassesThatImplementAnInterface?: boolean | 'public-fields'; }, ]; -type MessageIds = 'missingThis'; +export type MessageIds = 'missingThis'; export default createRule({ name: 'class-methods-use-this', diff --git a/packages/eslint-plugin/src/rules/comma-spacing.ts b/packages/eslint-plugin/src/rules/comma-spacing.ts index cd283e4c97ac..79e9450c012b 100644 --- a/packages/eslint-plugin/src/rules/comma-spacing.ts +++ b/packages/eslint-plugin/src/rules/comma-spacing.ts @@ -10,13 +10,13 @@ import { isTokenOnSameLine, } from '../util'; -type Options = [ +export type Options = [ { before: boolean; after: boolean; }, ]; -type MessageIds = 'missing' | 'unexpected'; +export type MessageIds = 'missing' | 'unexpected'; export default createRule({ name: 'comma-spacing', diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index 166a2a8ecfda..466fc83a544a 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -3,8 +3,8 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, nullThrows, NullThrowsReasons } from '../util'; -type MessageIds = 'preferConstructor' | 'preferTypeAnnotation'; -type Options = ['constructor' | 'type-annotation']; +export type MessageIds = 'preferConstructor' | 'preferTypeAnnotation'; +export type Options = ['constructor' | 'type-annotation']; export default createRule({ name: 'consistent-generic-constructors', diff --git a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts index f0f91cc32b8e..c85f49f71bf3 100644 --- a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts +++ b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts @@ -3,8 +3,8 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type MessageIds = 'preferIndexSignature' | 'preferRecord'; -type Options = ['index-signature' | 'record']; +export type MessageIds = 'preferIndexSignature' | 'preferRecord'; +export type Options = ['index-signature' | 'record']; export default createRule({ name: 'consistent-indexed-object-style', diff --git a/packages/eslint-plugin/src/rules/consistent-return.ts b/packages/eslint-plugin/src/rules/consistent-return.ts index 5d4cc3fb9256..8abb899ef7ce 100644 --- a/packages/eslint-plugin/src/rules/consistent-return.ts +++ b/packages/eslint-plugin/src/rules/consistent-return.ts @@ -11,10 +11,10 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('consistent-return'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; -type FunctionNode = +export type FunctionNode = | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression; diff --git a/packages/eslint-plugin/src/rules/consistent-type-exports.ts b/packages/eslint-plugin/src/rules/consistent-type-exports.ts index 236659d13adb..cbd3b3236303 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-exports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-exports.ts @@ -12,7 +12,7 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { fixMixedExportsWithInlineTypeSpecifier: boolean; }, @@ -32,7 +32,7 @@ interface ReportValueExport { inlineTypeSpecifiers: TSESTree.ExportSpecifier[]; } -type MessageIds = +export type MessageIds = | 'multipleExportsAreTypes' | 'singleExportIsType' | 'typeOverValue'; diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index f4e98d2d9e00..3dfcb8ced4e0 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -15,10 +15,10 @@ import { NullThrowsReasons, } from '../util'; -type Prefer = 'no-type-imports' | 'type-imports'; -type FixStyle = 'inline-type-imports' | 'separate-type-imports'; +export type Prefer = 'no-type-imports' | 'type-imports'; +export type FixStyle = 'inline-type-imports' | 'separate-type-imports'; -type Options = [ +export type Options = [ { prefer?: Prefer; disallowTypeAnnotations?: boolean; @@ -44,7 +44,7 @@ interface ReportValueImport { inlineTypeSpecifiers: TSESTree.ImportSpecifier[]; } -type MessageIds = +export type MessageIds = | 'typeOverValue' | 'someImportsAreOnlyTypes' | 'avoidImportType' diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index 9904dc2e5336..54e8c132c53d 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -9,7 +9,7 @@ import { isValidFunctionExpressionReturnType, } from '../util/explicitReturnTypeUtils'; -type Options = [ +export type Options = [ { allowExpressions?: boolean; allowTypedFunctionExpressions?: boolean; @@ -21,7 +21,7 @@ type Options = [ allowIIFEs?: boolean; }, ]; -type MessageIds = 'missingReturnType'; +export type MessageIds = 'missingReturnType'; type FunctionNode = | TSESTree.ArrowFunctionExpression diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index 5947d292f8bd..2a885d275d37 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -12,12 +12,12 @@ import { getParameterPropertyHeadLoc, } from '../util/getMemberHeadLoc'; -type AccessibilityLevel = +export type AccessibilityLevel = | 'explicit' // require an accessor (including public) | 'no-public' // don't require public | 'off'; // don't check -interface Config { +export interface Config { accessibility?: AccessibilityLevel; ignoredMethodNames?: string[]; overrides?: { @@ -29,9 +29,9 @@ interface Config { }; } -type Options = [Config]; +export type Options = [Config]; -type MessageIds = +export type MessageIds = | 'addExplicitAccessibility' | 'missingAccessibility' | 'unwantedPublicAccessibility'; diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index c8981e403941..66e606d20538 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -16,7 +16,7 @@ import { isTypedFunctionExpression, } from '../util/explicitReturnTypeUtils'; -type Options = [ +export type Options = [ { allowArgumentsExplicitlyTypedAsAny?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; @@ -25,7 +25,7 @@ type Options = [ allowTypedFunctionExpressions?: boolean; }, ]; -type MessageIds = +export type MessageIds = | 'anyTypedArg' | 'anyTypedArgUnnamed' | 'missingArgType' diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts index 248ecd0d7e8a..11a5d8313a7e 100644 --- a/packages/eslint-plugin/src/rules/indent.ts +++ b/packages/eslint-plugin/src/rules/indent.ts @@ -17,8 +17,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('indent'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; const KNOWN_NODES = new Set([ // Class properties aren't yet supported by eslint... diff --git a/packages/eslint-plugin/src/rules/lines-between-class-members.ts b/packages/eslint-plugin/src/rules/lines-between-class-members.ts index 60da9308757a..2304f7ac405b 100644 --- a/packages/eslint-plugin/src/rules/lines-between-class-members.ts +++ b/packages/eslint-plugin/src/rules/lines-between-class-members.ts @@ -11,8 +11,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('lines-between-class-members'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; const schema = Object.values( deepMerge( diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index 428f31f3667e..13a398defdf7 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -4,10 +4,10 @@ import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'; import { createRule, deepMerge } from '../util'; -type Delimiter = 'comma' | 'none' | 'semi'; +export type Delimiter = 'comma' | 'none' | 'semi'; // need type's implicit index sig for deepMerge // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -type TypeOptions = { +export type TypeOptions = { delimiter?: Delimiter; requireLast?: boolean; }; @@ -15,19 +15,19 @@ type TypeOptionsWithType = TypeOptions & { type: string; }; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -type BaseOptions = { +export type BaseOptions = { multiline?: TypeOptions; singleline?: TypeOptions; }; -type Config = BaseOptions & { +export type Config = BaseOptions & { overrides?: { typeLiteral?: BaseOptions; interface?: BaseOptions; }; multilineDetection?: 'brackets' | 'last-member'; }; -type Options = [Config]; -type MessageIds = +export type Options = [Config]; +export type MessageIds = | 'expectedComma' | 'expectedSemi' | 'unexpectedComma' diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 4713192770a7..c357095ae988 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -17,9 +17,9 @@ export type MessageIds = | 'incorrectOrder' | 'incorrectRequiredMembersOrder'; -type ReadonlyType = 'readonly-field' | 'readonly-signature'; +export type ReadonlyType = 'readonly-field' | 'readonly-signature'; -type MemberKind = +export type MemberKind = | ReadonlyType | 'accessor' | 'call-signature' @@ -31,7 +31,7 @@ type MemberKind = | 'signature' | 'static-initialization'; -type DecoratedMemberKind = +export type DecoratedMemberKind = | Exclude | 'accessor' | 'field' @@ -39,16 +39,16 @@ type DecoratedMemberKind = | 'method' | 'set'; -type NonCallableMemberKind = Exclude< +export type NonCallableMemberKind = Exclude< MemberKind, 'constructor' | 'readonly-signature' | 'signature' >; -type MemberScope = 'abstract' | 'instance' | 'static'; +export type MemberScope = 'abstract' | 'instance' | 'static'; -type Accessibility = TSESTree.Accessibility | '#private'; +export type Accessibility = TSESTree.Accessibility | '#private'; -type BaseMemberType = +export type BaseMemberType = | MemberKind | `${Accessibility}-${Exclude< MemberKind, @@ -59,26 +59,26 @@ type BaseMemberType = | `${MemberScope}-${NonCallableMemberKind}` | `decorated-${DecoratedMemberKind}`; -type MemberType = BaseMemberType | BaseMemberType[]; +export type MemberType = BaseMemberType | BaseMemberType[]; -type AlphabeticalOrder = +export type AlphabeticalOrder = | 'alphabetically-case-insensitive' | 'alphabetically' | 'natural-case-insensitive' | 'natural'; -type Order = AlphabeticalOrder | 'as-written'; +export type Order = AlphabeticalOrder | 'as-written'; -interface SortedOrderConfig { +export interface SortedOrderConfig { memberTypes?: MemberType[] | 'never'; optionalityOrder?: OptionalityOrder; order?: Order; } -type OrderConfig = MemberType[] | SortedOrderConfig | 'never'; -type Member = TSESTree.ClassElement | TSESTree.TypeElement; +export type OrderConfig = MemberType[] | SortedOrderConfig | 'never'; +export type Member = TSESTree.ClassElement | TSESTree.TypeElement; -type OptionalityOrder = 'optional-first' | 'required-first'; +export type OptionalityOrder = 'optional-first' | 'required-first'; export type Options = [ { diff --git a/packages/eslint-plugin/src/rules/no-array-delete.ts b/packages/eslint-plugin/src/rules/no-array-delete.ts index d9900a1df10f..96bd2fdc2568 100644 --- a/packages/eslint-plugin/src/rules/no-array-delete.ts +++ b/packages/eslint-plugin/src/rules/no-array-delete.ts @@ -8,7 +8,7 @@ import { getParserServices, } from '../util'; -type MessageId = 'noArrayDelete' | 'useSplice'; +export type MessageId = 'noArrayDelete' | 'useSplice'; export default createRule<[], MessageId>({ name: 'no-array-delete', diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 0369e66fe66f..0dee231a011f 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -10,12 +10,12 @@ enum Usefulness { Sometimes = 'may', } -type Options = [ +export type Options = [ { ignoredTypeNames?: string[]; }, ]; -type MessageIds = 'baseToString'; +export type MessageIds = 'baseToString'; export default createRule({ name: 'no-base-to-string', diff --git a/packages/eslint-plugin/src/rules/no-dupe-class-members.ts b/packages/eslint-plugin/src/rules/no-dupe-class-members.ts index 08dd0b35d3b8..e1ffecdd6dad 100644 --- a/packages/eslint-plugin/src/rules/no-dupe-class-members.ts +++ b/packages/eslint-plugin/src/rules/no-dupe-class-members.ts @@ -10,8 +10,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-dupe-class-members'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; export default createRule({ name: 'no-dupe-class-members', diff --git a/packages/eslint-plugin/src/rules/no-empty-function.ts b/packages/eslint-plugin/src/rules/no-empty-function.ts index 6a8e90ebaf13..79e6ffd9cc56 100644 --- a/packages/eslint-plugin/src/rules/no-empty-function.ts +++ b/packages/eslint-plugin/src/rules/no-empty-function.ts @@ -11,8 +11,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-empty-function'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; const schema = deepMerge( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- https://github.com/microsoft/TypeScript/issues/17002 diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts index 674b86d44bf9..0e56604d2567 100644 --- a/packages/eslint-plugin/src/rules/no-empty-interface.ts +++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts @@ -4,12 +4,12 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, isDefinitionFile } from '../util'; -type Options = [ +export type Options = [ { allowSingleExtends?: boolean; }, ]; -type MessageIds = 'noEmpty' | 'noEmptyWithSuper'; +export type MessageIds = 'noEmpty' | 'noEmptyWithSuper'; export default createRule({ name: 'no-empty-interface', diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index 0ed0f4c6b4de..bd1dde121c3f 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -13,8 +13,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-extra-parens'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; export default createRule({ name: 'no-extra-parens', diff --git a/packages/eslint-plugin/src/rules/no-extra-semi.ts b/packages/eslint-plugin/src/rules/no-extra-semi.ts index 4d68f2d6db46..d384cde41fc8 100644 --- a/packages/eslint-plugin/src/rules/no-extra-semi.ts +++ b/packages/eslint-plugin/src/rules/no-extra-semi.ts @@ -7,8 +7,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-extra-semi'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; export default createRule({ name: 'no-extra-semi', diff --git a/packages/eslint-plugin/src/rules/no-extraneous-class.ts b/packages/eslint-plugin/src/rules/no-extraneous-class.ts index a7c05f8cc5a8..6d975a454b9a 100644 --- a/packages/eslint-plugin/src/rules/no-extraneous-class.ts +++ b/packages/eslint-plugin/src/rules/no-extraneous-class.ts @@ -3,7 +3,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { allowConstructorOnly?: boolean; allowEmpty?: boolean; @@ -11,7 +11,7 @@ type Options = [ allowWithDecorator?: boolean; }, ]; -type MessageIds = 'empty' | 'onlyConstructor' | 'onlyStatic'; +export type MessageIds = 'empty' | 'onlyConstructor' | 'onlyStatic'; export default createRule({ name: 'no-extraneous-class', diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 1ae5e602ae0e..8bbc237803af 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -14,7 +14,7 @@ import { typeMatchesSpecifier, } from '../util'; -type Options = [ +export type Options = [ { ignoreVoid?: boolean; ignoreIIFE?: boolean; @@ -22,7 +22,7 @@ type Options = [ }, ]; -type MessageId = +export type MessageId = | 'floating' | 'floatingVoid' | 'floatingUselessRejectionHandler' diff --git a/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts b/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts index 1658d471bc7b..585e78a177de 100644 --- a/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts +++ b/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts @@ -9,8 +9,8 @@ import { NullThrowsReasons, } from '../util'; -type Options = []; -type MessageIds = 'useTopLevelQualifier'; +export type Options = []; +export type MessageIds = 'useTopLevelQualifier'; export default createRule({ name: 'no-import-type-side-effects', diff --git a/packages/eslint-plugin/src/rules/no-inferrable-types.ts b/packages/eslint-plugin/src/rules/no-inferrable-types.ts index 51ead2ae476a..3b76ee94b0c6 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -4,13 +4,13 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, nullThrows, NullThrowsReasons } from '../util'; -type Options = [ +export type Options = [ { ignoreParameters?: boolean; ignoreProperties?: boolean; }, ]; -type MessageIds = 'noInferrableType'; +export type MessageIds = 'noInferrableType'; export default createRule({ name: 'no-inferrable-types', diff --git a/packages/eslint-plugin/src/rules/no-invalid-void-type.ts b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts index 8ae1604a210b..4d4189ac7dfc 100644 --- a/packages/eslint-plugin/src/rules/no-invalid-void-type.ts +++ b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts @@ -3,12 +3,12 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -interface Options { +export interface Options { allowInGenericTypeArguments?: [string, ...string[]] | boolean; allowAsThisParameter?: boolean; } -type MessageIds = +export type MessageIds = | 'invalidVoidForGeneric' | 'invalidVoidNotReturn' | 'invalidVoidNotReturnOrGeneric' diff --git a/packages/eslint-plugin/src/rules/no-loop-func.ts b/packages/eslint-plugin/src/rules/no-loop-func.ts index a34217453b0d..096e3618f5d4 100644 --- a/packages/eslint-plugin/src/rules/no-loop-func.ts +++ b/packages/eslint-plugin/src/rules/no-loop-func.ts @@ -10,8 +10,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-loop-func'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; export default createRule({ name: 'no-loop-func', diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts index 06938f9e4dcf..51d2bdee9f25 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -9,8 +9,10 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-loss-of-precision'); -type Options = InferOptionsTypeFromRule>; -type MessageIds = InferMessageIdsTypeFromRule>; +export type Options = InferOptionsTypeFromRule>; +export type MessageIds = InferMessageIdsTypeFromRule< + NonNullable +>; export default createRule({ name: 'no-loss-of-precision', diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts index 5a86056cb90d..d31cdd895e36 100644 --- a/packages/eslint-plugin/src/rules/no-magic-numbers.ts +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -11,8 +11,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-magic-numbers'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; // Extend base schema with additional property to ignore TS numeric literal types const schema = deepMerge( diff --git a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts index e8fdde8d1f33..d1d21612476c 100644 --- a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts +++ b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { checkNever: boolean; }, diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 1a58d884dc7c..2c386402a162 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -9,7 +9,7 @@ import { isRestParameterDeclaration, } from '../util'; -type Options = [ +export type Options = [ { checksConditionals?: boolean; checksVoidReturn?: ChecksVoidReturnOptions | boolean; @@ -17,7 +17,7 @@ type Options = [ }, ]; -interface ChecksVoidReturnOptions { +export interface ChecksVoidReturnOptions { arguments?: boolean; attributes?: boolean; properties?: boolean; @@ -25,7 +25,7 @@ interface ChecksVoidReturnOptions { variables?: boolean; } -type MessageId = +export type MessageId = | 'conditional' | 'spread' | 'voidReturnArgument' diff --git a/packages/eslint-plugin/src/rules/no-namespace.ts b/packages/eslint-plugin/src/rules/no-namespace.ts index c6b9213259be..67a4979358ba 100644 --- a/packages/eslint-plugin/src/rules/no-namespace.ts +++ b/packages/eslint-plugin/src/rules/no-namespace.ts @@ -3,13 +3,13 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, isDefinitionFile } from '../util'; -type Options = [ +export type Options = [ { allowDeclarations?: boolean; allowDefinitionFiles?: boolean; }, ]; -type MessageIds = 'moduleSyntaxIsPreferred'; +export type MessageIds = 'moduleSyntaxIsPreferred'; export default createRule({ name: 'no-namespace', diff --git a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts index 8545b0e1c110..f34637eeb42d 100644 --- a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts @@ -8,7 +8,7 @@ import { NullThrowsReasons, } from '../util'; -type MessageIds = 'noNonNull' | 'suggestOptionalChain'; +export type MessageIds = 'noNonNull' | 'suggestOptionalChain'; export default createRule<[], MessageIds>({ name: 'no-non-null-assertion', diff --git a/packages/eslint-plugin/src/rules/no-redeclare.ts b/packages/eslint-plugin/src/rules/no-redeclare.ts index b084d90650b0..782df71aadbc 100644 --- a/packages/eslint-plugin/src/rules/no-redeclare.ts +++ b/packages/eslint-plugin/src/rules/no-redeclare.ts @@ -4,8 +4,11 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, getNameLocationInGlobalDirectiveComment } from '../util'; -type MessageIds = 'redeclared' | 'redeclaredAsBuiltin' | 'redeclaredBySyntax'; -type Options = [ +export type MessageIds = + | 'redeclared' + | 'redeclaredAsBuiltin' + | 'redeclaredBySyntax'; +export type Options = [ { builtinGlobals?: boolean; ignoreDeclarationMerge?: boolean; diff --git a/packages/eslint-plugin/src/rules/no-require-imports.ts b/packages/eslint-plugin/src/rules/no-require-imports.ts index 1956694484f4..577b36371905 100644 --- a/packages/eslint-plugin/src/rules/no-require-imports.ts +++ b/packages/eslint-plugin/src/rules/no-require-imports.ts @@ -3,12 +3,12 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import * as util from '../util'; -type Options = [ +export type Options = [ { allow: string[]; }, ]; -type MessageIds = 'noRequireImports'; +export type MessageIds = 'noRequireImports'; export default util.createRule({ name: 'no-require-imports', diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts index f66c21f6cdb1..78b59c53c775 100644 --- a/packages/eslint-plugin/src/rules/no-shadow.ts +++ b/packages/eslint-plugin/src/rules/no-shadow.ts @@ -8,8 +8,8 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type MessageIds = 'noShadow' | 'noShadowGlobal'; -type Options = [ +export type MessageIds = 'noShadow' | 'noShadowGlobal'; +export type Options = [ { allow?: string[]; builtinGlobals?: boolean; diff --git a/packages/eslint-plugin/src/rules/no-this-alias.ts b/packages/eslint-plugin/src/rules/no-this-alias.ts index 98006e56328d..5ddbc7463bf0 100644 --- a/packages/eslint-plugin/src/rules/no-this-alias.ts +++ b/packages/eslint-plugin/src/rules/no-this-alias.ts @@ -3,13 +3,13 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { allowDestructuring?: boolean; allowedNames?: string[]; }, ]; -type MessageIds = 'thisAssignment' | 'thisDestructure'; +export type MessageIds = 'thisAssignment' | 'thisDestructure'; export default createRule({ name: 'no-this-alias', diff --git a/packages/eslint-plugin/src/rules/no-throw-literal.ts b/packages/eslint-plugin/src/rules/no-throw-literal.ts index d893dbc164a5..2318c75b742e 100644 --- a/packages/eslint-plugin/src/rules/no-throw-literal.ts +++ b/packages/eslint-plugin/src/rules/no-throw-literal.ts @@ -10,9 +10,9 @@ import { isTypeUnknownType, } from '../util'; -type MessageIds = 'object' | 'undef'; +export type MessageIds = 'object' | 'undef'; -type Options = [ +export type Options = [ { allowThrowingAny?: boolean; allowThrowingUnknown?: boolean; diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index 53e64a3b5c69..cd7898f7e9c4 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -3,14 +3,14 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Values = +export type Values = | 'always' | 'in-intersections' | 'in-unions-and-intersections' | 'in-unions' | 'never'; -type Options = [ +export type Options = [ { allowAliases?: Values; allowCallbacks?: 'always' | 'never'; @@ -22,7 +22,7 @@ type Options = [ allowGenerics?: 'always' | 'never'; }, ]; -type MessageIds = 'noCompositionAlias' | 'noTypeAlias'; +export type MessageIds = 'noCompositionAlias' | 'noTypeAlias'; type CompositionType = | AST_NODE_TYPES.TSIntersectionType diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts index b472f75e5a0a..9900c20ed22e 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts @@ -5,14 +5,14 @@ import * as ts from 'typescript'; import { createRule, getParserServices, isStrongPrecedenceNode } from '../util'; -type MessageIds = +export type MessageIds = | 'comparingNullableToFalse' | 'comparingNullableToTrueDirect' | 'comparingNullableToTrueNegated' | 'direct' | 'negated'; -type Options = [ +export type Options = [ { allowComparingNullableBooleansToTrue?: boolean; allowComparingNullableBooleansToFalse?: boolean; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts index 44e10c5e33c8..aa35bda65f44 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -11,7 +11,7 @@ import { isUndefinedIdentifier, } from '../util'; -type MessageId = 'noUnnecessaryTemplateExpression'; +export type MessageId = 'noUnnecessaryTemplateExpression'; export default createRule<[], MessageId>({ name: 'no-unnecessary-template-expression', diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts index d482ef1b672a..711565a11d96 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts @@ -20,7 +20,7 @@ type ParameterCapableTSNode = | ts.TypeQueryNode | ts.TypeReferenceNode; -type MessageIds = 'unnecessaryTypeParameter'; +export type MessageIds = 'unnecessaryTypeParameter'; export default createRule<[], MessageIds>({ name: 'no-unnecessary-type-arguments', diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 69f5daaad619..b128178b6bc1 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -15,12 +15,12 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { typesToIgnore?: string[]; }, ]; -type MessageIds = 'contextuallyUnnecessary' | 'unnecessaryAssertion'; +export type MessageIds = 'contextuallyUnnecessary' | 'unnecessaryAssertion'; export default createRule({ name: 'no-unnecessary-type-assertion', diff --git a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts index 08950fa8d732..3b03af48065b 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts @@ -12,7 +12,7 @@ import { nullThrows, } from '../util'; -type MessageIds = +export type MessageIds = | 'unsafeArgument' | 'unsafeArraySpread' | 'unsafeSpread' diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index b4ec6379f2e1..7be407630cf9 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -9,7 +9,7 @@ import { isTypeAnyType, } from '../util'; -type MessageIds = +export type MessageIds = | 'unsafeCall' | 'unsafeCallThis' | 'unsafeNew' diff --git a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts index 66488e37124a..a95633404d1d 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-unary-minus.ts @@ -3,8 +3,8 @@ import * as ts from 'typescript'; import * as util from '../util'; -type Options = []; -type MessageIds = 'unaryMinus'; +export type Options = []; +export type MessageIds = 'unaryMinus'; export default util.createRule({ name: 'no-unsafe-unary-minus', diff --git a/packages/eslint-plugin/src/rules/no-unused-expressions.ts b/packages/eslint-plugin/src/rules/no-unused-expressions.ts index 83c2ddd6c527..aab3bbf4f5b5 100644 --- a/packages/eslint-plugin/src/rules/no-unused-expressions.ts +++ b/packages/eslint-plugin/src/rules/no-unused-expressions.ts @@ -9,8 +9,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-unused-expressions'); -type MessageIds = InferMessageIdsTypeFromRule; -type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; export default createRule({ name: 'no-unused-expressions', diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index d577773de9ef..551bfe9ecc39 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -219,7 +219,7 @@ function isInInitializer( return false; } -interface Config { +export interface Config { functions?: boolean; classes?: boolean; enums?: boolean; @@ -228,8 +228,8 @@ interface Config { ignoreTypeReferences?: boolean; allowNamedExports?: boolean; } -type Options = [Config | 'nofunc']; -type MessageIds = 'noUseBeforeDefine'; +export type Options = [Config | 'nofunc']; +export type MessageIds = 'noUseBeforeDefine'; export default createRule({ name: 'no-use-before-define', diff --git a/packages/eslint-plugin/src/rules/no-useless-constructor.ts b/packages/eslint-plugin/src/rules/no-useless-constructor.ts index dcfc7dd976de..460132f0cb33 100644 --- a/packages/eslint-plugin/src/rules/no-useless-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-useless-constructor.ts @@ -10,8 +10,8 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('no-useless-constructor'); -type Options = InferOptionsTypeFromRule; -type MessageIds = InferMessageIdsTypeFromRule; +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; /** * Check if method with accessibility is not useless diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 7b13cd8e2e9a..3dd793e79831 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -11,7 +11,7 @@ import { isUndefinedIdentifier, } from '../util'; -type MessageId = 'noUnnecessaryTemplateExpression'; +export type MessageId = 'noUnnecessaryTemplateExpression'; export default createRule<[], MessageId>({ name: 'no-useless-template-literals', diff --git a/packages/eslint-plugin/src/rules/no-var-requires.ts b/packages/eslint-plugin/src/rules/no-var-requires.ts index 9bb9fb7c1921..ec8e00078360 100644 --- a/packages/eslint-plugin/src/rules/no-var-requires.ts +++ b/packages/eslint-plugin/src/rules/no-var-requires.ts @@ -3,12 +3,12 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule, getStaticStringValue } from '../util'; -type Options = [ +export type Options = [ { allow: string[]; }, ]; -type MessageIds = 'noVarReqs'; +export type MessageIds = 'noVarReqs'; export default createRule({ name: 'no-var-requires', diff --git a/packages/eslint-plugin/src/rules/only-throw-error.ts b/packages/eslint-plugin/src/rules/only-throw-error.ts index 62ce268fc700..bc30a31b1428 100644 --- a/packages/eslint-plugin/src/rules/only-throw-error.ts +++ b/packages/eslint-plugin/src/rules/only-throw-error.ts @@ -10,9 +10,9 @@ import { isTypeUnknownType, } from '../util'; -type MessageIds = 'object' | 'undef'; +export type MessageIds = 'object' | 'undef'; -type Options = [ +export type Options = [ { allowThrowingAny?: boolean; allowThrowingUnknown?: boolean; diff --git a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts b/packages/eslint-plugin/src/rules/padding-line-between-statements.ts index ecd87a06a643..be4227903475 100644 --- a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts +++ b/packages/eslint-plugin/src/rules/padding-line-between-statements.ts @@ -35,14 +35,14 @@ interface NodeTestObject { test: NodeTest; } -interface PaddingOption { +export interface PaddingOption { blankLine: keyof typeof PaddingTypes; prev: string[] | string; next: string[] | string; } -type MessageIds = 'expectedBlankLine' | 'unexpectedBlankLine'; -type Options = PaddingOption[]; +export type MessageIds = 'expectedBlankLine' | 'unexpectedBlankLine'; +export type Options = PaddingOption[]; const LT = `[${Array.from( new Set(['\r\n', '\r', '\n', '\u2028', '\u2029']), diff --git a/packages/eslint-plugin/src/rules/parameter-properties.ts b/packages/eslint-plugin/src/rules/parameter-properties.ts index 5246594e912e..91408e162d37 100644 --- a/packages/eslint-plugin/src/rules/parameter-properties.ts +++ b/packages/eslint-plugin/src/rules/parameter-properties.ts @@ -3,7 +3,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, nullThrows } from '../util'; -type Modifier = +export type Modifier = | 'private readonly' | 'private' | 'protected readonly' @@ -12,16 +12,16 @@ type Modifier = | 'public' | 'readonly'; -type Prefer = 'class-property' | 'parameter-property'; +export type Prefer = 'class-property' | 'parameter-property'; -type Options = [ +export type Options = [ { allow?: Modifier[]; prefer?: Prefer; }, ]; -type MessageIds = 'preferClassProperty' | 'preferParameterProperty'; +export type MessageIds = 'preferClassProperty' | 'preferParameterProperty'; export default createRule({ name: 'parameter-properties', diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 60e53dbb61e6..a50f1f8fac46 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -13,13 +13,13 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('prefer-destructuring'); -type BaseOptions = InferOptionsTypeFromRule; -type EnforcementOptions = BaseOptions[1] & { +export type BaseOptions = InferOptionsTypeFromRule; +export type EnforcementOptions = BaseOptions[1] & { enforceForDeclarationWithTypeAnnotation?: boolean; }; -type Options = [BaseOptions[0], EnforcementOptions]; +export type Options = [BaseOptions[0], EnforcementOptions]; -type MessageIds = InferMessageIdsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; const destructuringTypeConfig: JSONSchema4 = { type: 'object', diff --git a/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts b/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts index 27572b4f8f7f..9ef9191cfa7d 100644 --- a/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts +++ b/packages/eslint-plugin/src/rules/prefer-enum-initializers.ts @@ -2,7 +2,7 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type MessageIds = 'defineInitializer' | 'defineInitializerSuggestion'; +export type MessageIds = 'defineInitializer' | 'defineInitializerSuggestion'; export default createRule<[], MessageIds>({ name: 'prefer-enum-initializers', diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/compareNodes.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/compareNodes.ts index d9dce486ec91..7d6acc5671f8 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/compareNodes.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/compareNodes.ts @@ -119,7 +119,7 @@ function compareByVisiting( return NodeComparisonResult.Equal; } -type CompareNodesArgument = TSESTree.Node | null | undefined; +export type CompareNodesArgument = TSESTree.Node | null | undefined; function compareNodesUncached( nodeA: TSESTree.Node, nodeB: TSESTree.Node, diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts index 54c44f4edda9..dd7beac839a8 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain-utils/gatherLogicalOperands.ts @@ -56,7 +56,7 @@ export interface ValidOperand { export interface InvalidOperand { type: OperandValidity.Invalid; } -type Operand = ValidOperand | InvalidOperand; +export type Operand = ValidOperand | InvalidOperand; const NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined; function isValidFalseBooleanCheckType( diff --git a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts index 6cb935c1db9a..9329d2c757fc 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts @@ -10,7 +10,7 @@ import { readonlynessOptionsSchema, } from '../util'; -type Options = [ +export type Options = [ { allow?: TypeOrValueSpecifier[]; checkParameterProperties?: boolean; @@ -18,7 +18,7 @@ type Options = [ treatMethodsAsReadonly?: boolean; }, ]; -type MessageIds = 'shouldBeReadonly'; +export type MessageIds = 'shouldBeReadonly'; export default createRule({ name: 'prefer-readonly-parameter-types', diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index f163b1aaab34..02adbc4640d0 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -14,8 +14,8 @@ import { getParameterPropertyHeadLoc, } from '../util/getMemberHeadLoc'; -type MessageIds = 'preferReadonly'; -type Options = [ +export type MessageIds = 'preferReadonly'; +export type Options = [ { onlyInlineLambdas?: boolean; }, diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index f6a22152043c..ac139d381e77 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -16,7 +16,7 @@ import { const EQ_OPERATORS = /^[=!]=/; const regexpp = new RegExpParser(); -type AllowedSingleElementEquality = 'always' | 'never'; +export type AllowedSingleElementEquality = 'always' | 'never'; export type Options = [ { @@ -24,7 +24,7 @@ export type Options = [ }, ]; -type MessageIds = 'preferEndsWith' | 'preferStartsWith'; +export type MessageIds = 'preferEndsWith' | 'preferStartsWith'; export default createRule({ name: 'prefer-string-starts-ends-with', diff --git a/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts b/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts index 6ae1f11720e1..4a6851686383 100644 --- a/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts +++ b/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts @@ -4,7 +4,7 @@ import type { RuleFix, RuleFixer } from '@typescript-eslint/utils/ts-eslint'; import { createRule } from '../util'; -type MessageIds = 'preferExpectErrorComment'; +export type MessageIds = 'preferExpectErrorComment'; export default createRule<[], MessageIds>({ name: 'prefer-ts-expect-error', diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index c191ce45b0db..3cb6a8c9b582 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -12,7 +12,7 @@ import { NullThrowsReasons, } from '../util'; -type Options = [ +export type Options = [ { allowAny?: boolean; allowedPromiseNames?: string[]; @@ -22,7 +22,7 @@ type Options = [ checkMethodDeclarations?: boolean; }, ]; -type MessageIds = 'missingAsync'; +export type MessageIds = 'missingAsync'; export default createRule({ name: 'promise-function-async', diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 1953c15c70cb..f70603498d8f 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -11,7 +11,7 @@ import { isTypeFlagSet, } from '../util'; -type Options = [ +export type Options = [ { allowAny?: boolean; allowBoolean?: boolean; @@ -22,7 +22,7 @@ type Options = [ }, ]; -type MessageIds = 'bigintAndNumber' | 'invalid' | 'mismatched'; +export type MessageIds = 'bigintAndNumber' | 'invalid' | 'mismatched'; export default createRule({ name: 'restrict-plus-operands', diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index cc719fe7fb7b..3447ddab7b3c 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -49,11 +49,11 @@ const optionTesters = ( option: `allow${type}` as const, tester, })); -type Options = [ +export type Options = [ { [Type in (typeof optionTesters)[number]['option']]?: boolean }, ]; -type MessageId = 'invalidType'; +export type MessageId = 'invalidType'; export default createRule({ name: 'restrict-template-expressions', diff --git a/packages/eslint-plugin/src/rules/space-before-function-paren.ts b/packages/eslint-plugin/src/rules/space-before-function-paren.ts index 78ea98239db7..3d8c813ccc46 100644 --- a/packages/eslint-plugin/src/rules/space-before-function-paren.ts +++ b/packages/eslint-plugin/src/rules/space-before-function-paren.ts @@ -4,8 +4,8 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, isOpeningParenToken } from '../util'; -type Option = 'always' | 'never'; -type FuncOption = Option | 'ignore'; +export type Option = 'always' | 'never'; +export type FuncOption = Option | 'ignore'; export type Options = [ | Option diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 1452d7de6cdb..e9a784c6374d 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -20,7 +20,7 @@ interface SwitchMetadata { readonly containsNonLiteralType: boolean; } -type Options = [ +export type Options = [ { /** * If `true`, allow `default` cases on switch statements with exhaustive @@ -39,7 +39,7 @@ type Options = [ }, ]; -type MessageIds = +export type MessageIds = | 'switchIsNotExhaustive' | 'dangerousDefaultCase' | 'addMissingCases'; diff --git a/packages/eslint-plugin/src/rules/triple-slash-reference.ts b/packages/eslint-plugin/src/rules/triple-slash-reference.ts index a45662c33d4c..7bec0ae018bf 100644 --- a/packages/eslint-plugin/src/rules/triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/triple-slash-reference.ts @@ -3,14 +3,14 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type Options = [ +export type Options = [ { lib?: 'always' | 'never'; path?: 'always' | 'never'; types?: 'always' | 'never' | 'prefer-import'; }, ]; -type MessageIds = 'tripleSlashReference'; +export type MessageIds = 'tripleSlashReference'; export default createRule({ name: 'triple-slash-reference', diff --git a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts index 47fbf80d8ac0..0f0988a3332b 100644 --- a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts +++ b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts @@ -31,8 +31,8 @@ interface Config extends WhitespaceRule { type WhitespaceRules = Required; -type Options = [Config?]; -type MessageIds = +export type Options = [Config?]; +export type MessageIds = | 'expectedSpaceAfter' | 'expectedSpaceBefore' | 'unexpectedSpaceAfter' diff --git a/packages/eslint-plugin/src/rules/typedef.ts b/packages/eslint-plugin/src/rules/typedef.ts index 187f4d620365..690386f2c9ce 100644 --- a/packages/eslint-plugin/src/rules/typedef.ts +++ b/packages/eslint-plugin/src/rules/typedef.ts @@ -14,9 +14,9 @@ const enum OptionKeys { VariableDeclarationIgnoreFunction = 'variableDeclarationIgnoreFunction', } -type Options = { [k in OptionKeys]?: boolean }; +export type Options = { [k in OptionKeys]?: boolean }; -type MessageIds = 'expectedTypedef' | 'expectedTypedefNamed'; +export type MessageIds = 'expectedTypedef' | 'expectedTypedefNamed'; export default createRule<[Options], MessageIds>({ name: 'typedef', diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index 9b416ea4a570..6a87b9ea6f78 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -14,7 +14,7 @@ import { // Rule Definition //------------------------------------------------------------------------------ -interface Config { +export interface Config { ignoreStatic: boolean; } diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts index c8fa2f85f7df..14db3f84df78 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -51,12 +51,12 @@ type MethodDefinition = | TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition; -type MessageIds = +export type MessageIds = | 'omittingRestParameter' | 'omittingSingleParameter' | 'singleParameterDifference'; -type Options = [ +export type Options = [ { ignoreDifferentlyNamedParameters?: boolean; }, diff --git a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts index b899b23c391d..74d09f305701 100644 --- a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts +++ b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts @@ -16,7 +16,7 @@ import { nullThrows, } from '../util'; -type MessageIds = +export type MessageIds = | 'useUnknown' | 'useUnknownSpreadArgs' | 'useUnknownArrayDestructuringPattern' diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index be28069d2877..761d7e8d0f82 100644 --- a/packages/eslint-plugin/src/util/getESLintCoreRule.ts +++ b/packages/eslint-plugin/src/util/getESLintCoreRule.ts @@ -1,7 +1,7 @@ import { ESLintUtils } from '@typescript-eslint/utils'; import { builtinRules } from 'eslint/use-at-your-own-risk'; -interface RuleMap { +export interface RuleMap { /* eslint-disable @typescript-eslint/consistent-type-imports -- more concise to use inline imports */ 'arrow-parens': typeof import('eslint/lib/rules/arrow-parens'); 'block-spacing': typeof import('eslint/lib/rules/block-spacing'); @@ -42,7 +42,7 @@ interface RuleMap { /* eslint-enable @typescript-eslint/consistent-type-imports */ } -type RuleId = keyof RuleMap; +export type RuleId = keyof RuleMap; export const getESLintCoreRule = (ruleId: R): RuleMap[R] => ESLintUtils.nullThrows( diff --git a/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts index a8f6bc40afbd..f1aa6c9ce68c 100644 --- a/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts +++ b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts @@ -5,7 +5,7 @@ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'; import { isArrowToken, isOpeningParenToken } from './astUtils'; -type FunctionNode = +export type FunctionNode = | TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; diff --git a/packages/eslint-plugin/src/util/getOperatorPrecedence.ts b/packages/eslint-plugin/src/util/getOperatorPrecedence.ts index 8aac3d6fbd2d..d14c05e963a6 100644 --- a/packages/eslint-plugin/src/util/getOperatorPrecedence.ts +++ b/packages/eslint-plugin/src/util/getOperatorPrecedence.ts @@ -295,7 +295,7 @@ export function getOperatorPrecedenceForNode( } } -type TSESTreeOperatorKind = +export type TSESTreeOperatorKind = | ValueOf | ValueOf; diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index d5d07b6ba7e1..4c95b7fd4db4 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -5,7 +5,7 @@ import { ESLintUtils, } from '@typescript-eslint/utils'; -interface WrappingFixerParams { +export interface WrappingFixerParams { /** Source code. */ sourceCode: Readonly; /** The node we want to modify. */ diff --git a/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts b/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts index 7e1207a222d2..90229443cf7c 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/cases/createTestCases.ts @@ -77,7 +77,7 @@ const IGNORED_FILTER = { regex: /.gnored/.source, }; -type Cases = { code: string[]; options: Omit }[]; +export type Cases = { code: string[]; options: Omit }[]; export function createTestCases(cases: Cases): void { const createValidTestCases = (): TSESLint.ValidTestCase[] => diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain/base-cases.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain/base-cases.ts index 28b75a91697d..d61a5556ccca 100644 --- a/packages/eslint-plugin/tests/rules/prefer-optional-chain/base-cases.ts +++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain/base-cases.ts @@ -5,8 +5,8 @@ import type { PreferOptionalChainOptions, } from '../../../src/rules/prefer-optional-chain-utils/PreferOptionalChainOptions'; -type MutateFn = (c: string) => string; -type BaseCaseCreator = (args: { +export type MutateFn = (c: string) => string; +export type BaseCaseCreator = (args: { operator: '&&' | '||'; mutateCode?: MutateFn; mutateOutput?: MutateFn; From 88713cb2097c3c2fffc8cc50097d64dbef56883b Mon Sep 17 00:00:00 2001 From: StyleShit Date: Mon, 8 Jul 2024 08:21:00 +0300 Subject: [PATCH 65/86] support keyof & typeof --- .../src/rules/require-types-exports.ts | 69 +++++++++++++-- .../tests/rules/require-types-exports.test.ts | 84 +++++++++++++++++++ 2 files changed, 146 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index e9315938ef9b..222bdeb1edc7 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -104,15 +104,16 @@ export default createRule<[], MessageIds>({ } function checkNodeTypes(node: TSESTree.Node): void { - const typeReferences = getTypeReferencesRecursively( + const { typeReferences, typeQueries } = getVisibleTypesRecursively( node, context.sourceCode, ); - typeReferences.forEach(checkTypeNode); + typeReferences.forEach(checkTypeReference); + typeQueries.forEach(checkTypeQuery); } - function checkTypeNode(node: TSESTree.TSTypeReference): void { + function checkTypeReference(node: TSESTree.TSTypeReference): void { const name = getTypeName(node.typeName); const isExternalized = externalizedTypes.has(name); @@ -123,7 +124,25 @@ export default createRule<[], MessageIds>({ } context.report({ - node: node, + node, + messageId: 'requireTypeExport', + data: { + name, + }, + }); + + reportedTypes.add(name); + } + + function checkTypeQuery(node: TSESTree.TSTypeQuery): void { + const name = context.sourceCode.getText(node); + const isReported = reportedTypes.has(name); + + if (isReported) { + return; + } + context.report({ + node, messageId: 'requireTypeExport', data: { name, @@ -187,11 +206,15 @@ function getTypeName(typeName: TSESTree.EntityName): string { } } -function getTypeReferencesRecursively( +function getVisibleTypesRecursively( node: TSESTree.Node, sourceCode: TSESLint.SourceCode, -): Set { +): { + typeReferences: Set; + typeQueries: Set; +} { const typeReferences = new Set(); + const typeQueries = new Set(); const visited = new Set(); collect(node); @@ -321,6 +344,16 @@ function getTypeReferencesRecursively( break; } + case AST_NODE_TYPES.TSTypeOperator: + collect(node.typeAnnotation); + break; + + case AST_NODE_TYPES.TSTypeQuery: + if (isInsideFunctionDeclaration(node)) { + typeQueries.add(node); + } + break; + case AST_NODE_TYPES.TSArrayType: collect(node.elementType); break; @@ -373,7 +406,29 @@ function getTypeReferencesRecursively( } } - return typeReferences; + return { + typeReferences, + typeQueries, + }; +} + +function isInsideFunctionDeclaration(node: TSESTree.Node): boolean { + const functionNodes = new Set([ + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.TSDeclareFunction, + ]); + + if (!node.parent) { + return false; + } + + if (functionNodes.has(node.parent.type)) { + return true; + } + + return isInsideFunctionDeclaration(node.parent); } function collectFunctionReturnStatements( diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index c5ddc08feb75..5629431c0711 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -458,6 +458,25 @@ ruleTester.run('require-types-exports', rule, { `, 'export type ValueOf = T[keyof T];', + + ` + const fruits = { apple: 'apple' }; + export type Fruits = typeof fruits; + + export function getFruit(key: Key): Fruits[Key] { + return fruits[key]; + } + `, + + ` + const fruits = { apple: 'apple' }; + + export function doWork(): number { + const fruit: keyof typeof fruits = 'apple'; + + return 1; + } + `, ], invalid: [ @@ -3405,5 +3424,70 @@ ruleTester.run('require-types-exports', rule, { }, ], }, + + { + code: ` + const fruits = { apple: 'apple' }; + + export function getFruit( + key: Key, + ): (typeof fruits)[Key] { + return fruits[key]; + } + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 52, + endColumn: 65, + data: { + name: 'typeof fruits', + }, + }, + ], + }, + + { + code: ` + const fruits = { apple: 'apple' }; + + export declare function processFruit( + fruit: F, + ): void; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 4, + column: 56, + endColumn: 75, + data: { + name: 'typeof fruits.apple', + }, + }, + ], + }, + + { + code: ` + const fruits = { apple: 'apple' }; + + export declare function processFruit< + F extends Record, + >(fruit: F): void; + `, + errors: [ + { + messageId: 'requireTypeExport', + line: 5, + column: 34, + endColumn: 47, + data: { + name: 'typeof fruits', + }, + }, + ], + }, ], }); From ff2c0a8692686d795968dd8f8ad8577de4aeca49 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Mon, 8 Jul 2024 08:36:10 +0300 Subject: [PATCH 66/86] simplify tsconfig --- packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json b/packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json index 87cfb2c4b422..6168cfcb8d54 100644 --- a/packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json +++ b/packages/eslint-plugin/tests/fixtures/tsconfig-with-dom.json @@ -1,9 +1,6 @@ { + "extends": "./tsconfig.json", "compilerOptions": { - "target": "es5", - "module": "ESNext", - "strict": true, - "esModuleInterop": true, "lib": ["esnext", "DOM"] } } From a03713a08ef6cb01e58311aeed36e7db9f1be83d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 14:17:24 -0500 Subject: [PATCH 67/86] Revert unintentional changes --- .../eslint-plugin/src/rules/comma-spacing.ts | 201 ----- packages/eslint-plugin/src/rules/indent.ts | 492 ----------- .../src/rules/lines-between-class-members.ts | 78 -- .../src/rules/member-delimiter-style.ts | 352 -------- .../src/rules/no-extra-parens.ts | 308 ------- .../eslint-plugin/src/rules/no-extra-semi.ts | 41 - .../src/rules/no-throw-literal.ts | 99 --- .../src/rules/no-useless-template-literals.ts | 176 ---- .../rules/padding-line-between-statements.ts | 825 ------------------ .../src/rules/space-before-function-paren.ts | 196 ----- .../src/rules/type-annotation-spacing.ts | 289 ------ 11 files changed, 3057 deletions(-) delete mode 100644 packages/eslint-plugin/src/rules/comma-spacing.ts delete mode 100644 packages/eslint-plugin/src/rules/indent.ts delete mode 100644 packages/eslint-plugin/src/rules/lines-between-class-members.ts delete mode 100644 packages/eslint-plugin/src/rules/member-delimiter-style.ts delete mode 100644 packages/eslint-plugin/src/rules/no-extra-parens.ts delete mode 100644 packages/eslint-plugin/src/rules/no-extra-semi.ts delete mode 100644 packages/eslint-plugin/src/rules/no-throw-literal.ts delete mode 100644 packages/eslint-plugin/src/rules/no-useless-template-literals.ts delete mode 100644 packages/eslint-plugin/src/rules/padding-line-between-statements.ts delete mode 100644 packages/eslint-plugin/src/rules/space-before-function-paren.ts delete mode 100644 packages/eslint-plugin/src/rules/type-annotation-spacing.ts diff --git a/packages/eslint-plugin/src/rules/comma-spacing.ts b/packages/eslint-plugin/src/rules/comma-spacing.ts deleted file mode 100644 index 79e9450c012b..000000000000 --- a/packages/eslint-plugin/src/rules/comma-spacing.ts +++ /dev/null @@ -1,201 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_TOKEN_TYPES } from '@typescript-eslint/utils'; - -import { - createRule, - isClosingBraceToken, - isClosingBracketToken, - isClosingParenToken, - isCommaToken, - isTokenOnSameLine, -} from '../util'; - -export type Options = [ - { - before: boolean; - after: boolean; - }, -]; -export type MessageIds = 'missing' | 'unexpected'; - -export default createRule({ - name: 'comma-spacing', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/comma-spacing'], - type: 'layout', - docs: { - description: 'Enforce consistent spacing before and after commas', - extendsBaseRule: true, - }, - fixable: 'whitespace', - schema: [ - { - type: 'object', - properties: { - before: { - type: 'boolean', - default: false, - }, - after: { - type: 'boolean', - default: true, - }, - }, - additionalProperties: false, - }, - ], - messages: { - unexpected: `There should be no space {{loc}} ','.`, - missing: `A space is required {{loc}} ','.`, - }, - }, - defaultOptions: [ - { - before: false, - after: true, - }, - ], - create(context, [{ before: spaceBefore, after: spaceAfter }]) { - const tokensAndComments = context.sourceCode.tokensAndComments; - const ignoredTokens = new Set(); - - /** - * Adds null elements of the ArrayExpression or ArrayPattern node to the ignore list - * @param node node to evaluate - */ - function addNullElementsToIgnoreList( - node: TSESTree.ArrayExpression | TSESTree.ArrayPattern, - ): void { - let previousToken = context.sourceCode.getFirstToken(node); - for (const element of node.elements) { - let token: TSESTree.Token | null; - if (element == null) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - token = context.sourceCode.getTokenAfter(previousToken!); - if (token && isCommaToken(token)) { - ignoredTokens.add(token); - } - } else { - token = context.sourceCode.getTokenAfter(element); - } - - previousToken = token; - } - } - - /** - * Adds type parameters trailing comma token to the ignore list - * @param node node to evaluate - */ - function addTypeParametersTrailingCommaToIgnoreList( - node: TSESTree.TSTypeParameterDeclaration, - ): void { - const paramLength = node.params.length; - if (paramLength) { - const param = node.params[paramLength - 1]; - const afterToken = context.sourceCode.getTokenAfter(param); - if (afterToken && isCommaToken(afterToken)) { - ignoredTokens.add(afterToken); - } - } - } - - /** - * Validates the spacing around a comma token. - * @param commaToken The token representing the comma - * @param prevToken The last token before the comma - * @param nextToken The first token after the comma - */ - function validateCommaSpacing( - commaToken: TSESTree.PunctuatorToken, - prevToken: TSESTree.Token | null, - nextToken: TSESTree.Token | null, - ): void { - if ( - prevToken && - isTokenOnSameLine(prevToken, commaToken) && - spaceBefore !== context.sourceCode.isSpaceBetween(prevToken, commaToken) - ) { - context.report({ - node: commaToken, - data: { - loc: 'before', - }, - messageId: spaceBefore ? 'missing' : 'unexpected', - fix: fixer => - spaceBefore - ? fixer.insertTextBefore(commaToken, ' ') - : fixer.replaceTextRange( - [prevToken.range[1], commaToken.range[0]], - '', - ), - }); - } - - if (nextToken && isClosingParenToken(nextToken)) { - return; - } - - if ( - spaceAfter && - nextToken && - (isClosingBraceToken(nextToken) || isClosingBracketToken(nextToken)) - ) { - return; - } - - if (!spaceAfter && nextToken && nextToken.type === AST_TOKEN_TYPES.Line) { - return; - } - - if ( - nextToken && - isTokenOnSameLine(commaToken, nextToken) && - spaceAfter !== context.sourceCode.isSpaceBetween(commaToken, nextToken) - ) { - context.report({ - node: commaToken, - data: { - loc: 'after', - }, - messageId: spaceAfter ? 'missing' : 'unexpected', - fix: fixer => - spaceAfter - ? fixer.insertTextAfter(commaToken, ' ') - : fixer.replaceTextRange( - [commaToken.range[1], nextToken.range[0]], - '', - ), - }); - } - } - - return { - TSTypeParameterDeclaration: addTypeParametersTrailingCommaToIgnoreList, - ArrayExpression: addNullElementsToIgnoreList, - ArrayPattern: addNullElementsToIgnoreList, - - 'Program:exit'(): void { - tokensAndComments.forEach((token, i) => { - if (!isCommaToken(token)) { - return; - } - - const prevToken = tokensAndComments[i - 1]; - const nextToken = tokensAndComments.at(i + 1); - - validateCommaSpacing( - token, - isCommaToken(prevToken) || ignoredTokens.has(token) - ? null - : prevToken, - (nextToken && isCommaToken(nextToken)) || ignoredTokens.has(token) - ? null - : nextToken ?? null, - ); - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts deleted file mode 100644 index 11a5d8313a7e..000000000000 --- a/packages/eslint-plugin/src/rules/indent.ts +++ /dev/null @@ -1,492 +0,0 @@ -/** - * Note this file is rather type-unsafe in its current state. - * This is due to some really funky type conversions between different node types. - * This is done intentionally based on the internal implementation of the base indent rule. - */ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, eslint-plugin/no-property-in-node */ - -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('indent'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -const KNOWN_NODES = new Set([ - // Class properties aren't yet supported by eslint... - AST_NODE_TYPES.PropertyDefinition, - - // ts keywords - AST_NODE_TYPES.TSAbstractKeyword, - AST_NODE_TYPES.TSAnyKeyword, - AST_NODE_TYPES.TSBooleanKeyword, - AST_NODE_TYPES.TSNeverKeyword, - AST_NODE_TYPES.TSNumberKeyword, - AST_NODE_TYPES.TSStringKeyword, - AST_NODE_TYPES.TSSymbolKeyword, - AST_NODE_TYPES.TSUndefinedKeyword, - AST_NODE_TYPES.TSUnknownKeyword, - AST_NODE_TYPES.TSVoidKeyword, - AST_NODE_TYPES.TSNullKeyword, - - // ts specific nodes we want to support - AST_NODE_TYPES.TSAbstractPropertyDefinition, - AST_NODE_TYPES.TSAbstractMethodDefinition, - AST_NODE_TYPES.TSArrayType, - AST_NODE_TYPES.TSAsExpression, - AST_NODE_TYPES.TSCallSignatureDeclaration, - AST_NODE_TYPES.TSConditionalType, - AST_NODE_TYPES.TSConstructorType, - AST_NODE_TYPES.TSConstructSignatureDeclaration, - AST_NODE_TYPES.TSDeclareFunction, - AST_NODE_TYPES.TSEmptyBodyFunctionExpression, - AST_NODE_TYPES.TSEnumDeclaration, - AST_NODE_TYPES.TSEnumMember, - AST_NODE_TYPES.TSExportAssignment, - AST_NODE_TYPES.TSExternalModuleReference, - AST_NODE_TYPES.TSFunctionType, - AST_NODE_TYPES.TSImportType, - AST_NODE_TYPES.TSIndexedAccessType, - AST_NODE_TYPES.TSIndexSignature, - AST_NODE_TYPES.TSInferType, - AST_NODE_TYPES.TSInterfaceBody, - AST_NODE_TYPES.TSInterfaceDeclaration, - AST_NODE_TYPES.TSInterfaceHeritage, - AST_NODE_TYPES.TSIntersectionType, - AST_NODE_TYPES.TSImportEqualsDeclaration, - AST_NODE_TYPES.TSLiteralType, - AST_NODE_TYPES.TSMappedType, - AST_NODE_TYPES.TSMethodSignature, - 'TSMinusToken', - AST_NODE_TYPES.TSModuleBlock, - AST_NODE_TYPES.TSModuleDeclaration, - AST_NODE_TYPES.TSNonNullExpression, - AST_NODE_TYPES.TSParameterProperty, - 'TSPlusToken', - AST_NODE_TYPES.TSPropertySignature, - AST_NODE_TYPES.TSQualifiedName, - 'TSQuestionToken', - AST_NODE_TYPES.TSRestType, - AST_NODE_TYPES.TSThisType, - AST_NODE_TYPES.TSTupleType, - AST_NODE_TYPES.TSTypeAnnotation, - AST_NODE_TYPES.TSTypeLiteral, - AST_NODE_TYPES.TSTypeOperator, - AST_NODE_TYPES.TSTypeParameter, - AST_NODE_TYPES.TSTypeParameterDeclaration, - AST_NODE_TYPES.TSTypeParameterInstantiation, - AST_NODE_TYPES.TSTypeReference, - AST_NODE_TYPES.TSUnionType, - AST_NODE_TYPES.Decorator, -]); - -export default createRule({ - name: 'indent', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/indent'], - type: 'layout', - docs: { - description: 'Enforce consistent indentation', - // too opinionated to be recommended - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: baseRule.meta.messages, - }, - defaultOptions: [ - // typescript docs and playground use 4 space indent - 4, - { - // typescript docs indent the case from the switch - // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-8.html#example-4 - SwitchCase: 1, - flatTernaryExpressions: false, - ignoredNodes: [], - }, - ], - create(context, optionsWithDefaults) { - // because we extend the base rule, have to update opts on the context - // the context defines options as readonly though... - const contextWithDefaults: typeof context = Object.create(context, { - options: { - writable: false, - configurable: false, - value: optionsWithDefaults, - }, - }); - - const rules = baseRule.create(contextWithDefaults); - - /** - * Converts from a TSPropertySignature to a Property - * @param node a TSPropertySignature node - * @param [type] the type to give the new node - * @returns a Property node - */ - function TSPropertySignatureToProperty( - node: - | TSESTree.TSEnumMember - | TSESTree.TSPropertySignature - | TSESTree.TypeElement, - type: - | AST_NODE_TYPES.Property - | AST_NODE_TYPES.PropertyDefinition = AST_NODE_TYPES.Property, - ): TSESTree.Node | null { - const base = { - // indent doesn't actually use these - key: null as any, - value: null as any, - - // Property flags - computed: false, - method: false, - kind: 'init', - // this will stop eslint from interrogating the type literal - shorthand: true, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }; - if (type === AST_NODE_TYPES.Property) { - return { - type, - ...base, - } as TSESTree.Property; - } - return { - type, - accessibility: undefined, - declare: false, - decorators: [], - definite: false, - optional: false, - override: false, - readonly: false, - static: false, - typeAnnotation: undefined, - ...base, - } as TSESTree.PropertyDefinition; - } - - return Object.assign({}, rules, { - // overwrite the base rule here so we can use our KNOWN_NODES list instead - '*:exit'(node: TSESTree.Node) { - // For nodes we care about, skip the default handling, because it just marks the node as ignored... - if (!KNOWN_NODES.has(node.type)) { - rules['*:exit'](node); - } - }, - - VariableDeclaration(node: TSESTree.VariableDeclaration) { - // https://github.com/typescript-eslint/typescript-eslint/issues/441 - if (node.declarations.length === 0) { - return; - } - - return rules.VariableDeclaration(node); - }, - - TSAsExpression(node: TSESTree.TSAsExpression) { - // transform it to a BinaryExpression - return rules['BinaryExpression, LogicalExpression']({ - type: AST_NODE_TYPES.BinaryExpression, - operator: 'as' as any, - left: node.expression, - // the first typeAnnotation includes the as token - right: node.typeAnnotation as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSConditionalType(node: TSESTree.TSConditionalType) { - // transform it to a ConditionalExpression - return rules.ConditionalExpression({ - type: AST_NODE_TYPES.ConditionalExpression, - test: { - parent: node, - type: AST_NODE_TYPES.BinaryExpression, - operator: 'extends' as any, - left: node.checkType as any, - right: node.extendsType as any, - - // location data - range: [node.checkType.range[0], node.extendsType.range[1]], - loc: { - start: node.checkType.loc.start, - end: node.extendsType.loc.end, - }, - }, - consequent: node.trueType as any, - alternate: node.falseType as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - 'TSEnumDeclaration, TSTypeLiteral'( - node: TSESTree.TSEnumDeclaration | TSESTree.TSTypeLiteral, - ) { - // transform it to an ObjectExpression - return rules['ObjectExpression, ObjectPattern']({ - type: AST_NODE_TYPES.ObjectExpression, - properties: ( - node.members as (TSESTree.TSEnumMember | TSESTree.TypeElement)[] - ).map( - member => - TSPropertySignatureToProperty(member) as TSESTree.Property, - ), - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSImportEqualsDeclaration(node: TSESTree.TSImportEqualsDeclaration) { - // transform it to an VariableDeclaration - // use VariableDeclaration instead of ImportDeclaration because it's essentially the same thing - const { id, moduleReference } = node; - - return rules.VariableDeclaration({ - type: AST_NODE_TYPES.VariableDeclaration, - kind: 'const' as const, - declarations: [ - { - type: AST_NODE_TYPES.VariableDeclarator, - range: [id.range[0], moduleReference.range[1]], - loc: { - start: id.loc.start, - end: moduleReference.loc.end, - }, - id: id, - init: { - type: AST_NODE_TYPES.CallExpression, - callee: { - type: AST_NODE_TYPES.Identifier, - name: 'require', - range: [ - moduleReference.range[0], - moduleReference.range[0] + 'require'.length, - ], - loc: { - start: moduleReference.loc.start, - end: { - line: moduleReference.loc.end.line, - column: moduleReference.loc.start.line + 'require'.length, - }, - }, - }, - arguments: - 'expression' in moduleReference - ? [moduleReference.expression] - : [], - - // location data - range: moduleReference.range, - loc: moduleReference.loc, - }, - } as TSESTree.VariableDeclarator, - ], - declare: false, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSIndexedAccessType(node: TSESTree.TSIndexedAccessType) { - // convert to a MemberExpression - return rules['MemberExpression, JSXMemberExpression, MetaProperty']({ - type: AST_NODE_TYPES.MemberExpression, - object: node.objectType as any, - property: node.indexType as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - optional: false, - computed: true, - }); - }, - - TSInterfaceBody(node: TSESTree.TSInterfaceBody) { - // transform it to an ClassBody - return rules['BlockStatement, ClassBody']({ - type: AST_NODE_TYPES.ClassBody, - body: node.body.map( - p => - TSPropertySignatureToProperty( - p, - AST_NODE_TYPES.PropertyDefinition, - ) as TSESTree.PropertyDefinition, - ), - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - 'TSInterfaceDeclaration[extends.length > 0]'( - node: TSESTree.TSInterfaceDeclaration, - ) { - // transform it to a ClassDeclaration - return rules[ - 'ClassDeclaration[superClass], ClassExpression[superClass]' - ]({ - type: AST_NODE_TYPES.ClassDeclaration, - body: node.body as any, - id: null, - // TODO: This is invalid, there can be more than one extends in interface - superClass: node.extends[0].expression as any, - abstract: false, - declare: false, - decorators: [], - implements: [], - superTypeArguments: undefined, - superTypeParameters: undefined, - typeParameters: undefined, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSMappedType(node: TSESTree.TSMappedType) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const squareBracketStart = context.sourceCode.getTokenBefore( - node.typeParameter, - )!; - - // transform it to an ObjectExpression - return rules['ObjectExpression, ObjectPattern']({ - type: AST_NODE_TYPES.ObjectExpression, - properties: [ - { - parent: node, - type: AST_NODE_TYPES.Property, - key: node.typeParameter as any, - value: node.typeAnnotation as any, - - // location data - range: [ - squareBracketStart.range[0], - node.typeAnnotation - ? node.typeAnnotation.range[1] - : squareBracketStart.range[0], - ], - loc: { - start: squareBracketStart.loc.start, - end: node.typeAnnotation - ? node.typeAnnotation.loc.end - : squareBracketStart.loc.end, - }, - kind: 'init' as const, - computed: false, - method: false, - optional: false, - shorthand: false, - }, - ], - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSModuleBlock(node: TSESTree.TSModuleBlock) { - // transform it to a BlockStatement - return rules['BlockStatement, ClassBody']({ - type: AST_NODE_TYPES.BlockStatement, - body: node.body as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSQualifiedName(node: TSESTree.TSQualifiedName) { - return rules['MemberExpression, JSXMemberExpression, MetaProperty']({ - type: AST_NODE_TYPES.MemberExpression, - object: node.left as any, - property: node.right as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - optional: false, - computed: false, - }); - }, - - TSTupleType(node: TSESTree.TSTupleType) { - // transform it to an ArrayExpression - return rules['ArrayExpression, ArrayPattern']({ - type: AST_NODE_TYPES.ArrayExpression, - elements: node.elementTypes as any, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - - TSTypeParameterDeclaration(node: TSESTree.TSTypeParameterDeclaration) { - if (!node.params.length) { - return; - } - - const [name, ...attributes] = node.params; - - // JSX is about the closest we can get because the angle brackets - // it's not perfect but it works! - return rules.JSXOpeningElement({ - type: AST_NODE_TYPES.JSXOpeningElement, - selfClosing: false, - name: name as any, - attributes: attributes as any, - typeArguments: undefined, - typeParameters: undefined, - - // location data - parent: node.parent, - range: node.range, - loc: node.loc, - }); - }, - }); - }, -}); diff --git a/packages/eslint-plugin/src/rules/lines-between-class-members.ts b/packages/eslint-plugin/src/rules/lines-between-class-members.ts deleted file mode 100644 index 2304f7ac405b..000000000000 --- a/packages/eslint-plugin/src/rules/lines-between-class-members.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, deepMerge } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('lines-between-class-members'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -const schema = Object.values( - deepMerge( - { ...baseRule.meta.schema }, - { - 1: { - properties: { - exceptAfterOverload: { - type: 'boolean', - default: true, - }, - }, - }, - }, - ), -) as JSONSchema4[]; - -export default createRule({ - name: 'lines-between-class-members', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/lines-between-class-members'], - type: 'layout', - docs: { - description: 'Require or disallow an empty line between class members', - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: baseRule.meta.hasSuggestions, - schema, - messages: baseRule.meta.messages, - }, - defaultOptions: [ - 'always', - { - exceptAfterOverload: true, - exceptAfterSingleLine: false, - }, - ], - create(context, [firstOption, secondOption]) { - const rules = baseRule.create(context); - const exceptAfterOverload = - secondOption?.exceptAfterOverload && firstOption === 'always'; - - function isOverload(node: TSESTree.Node): boolean { - return ( - (node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || - node.type === AST_NODE_TYPES.MethodDefinition) && - node.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression - ); - } - - return { - ClassBody(node): void { - const body = exceptAfterOverload - ? node.body.filter(node => !isOverload(node)) - : node.body; - - rules.ClassBody({ ...node, body }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts deleted file mode 100644 index 13a398defdf7..000000000000 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ /dev/null @@ -1,352 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'; - -import { createRule, deepMerge } from '../util'; - -export type Delimiter = 'comma' | 'none' | 'semi'; -// need type's implicit index sig for deepMerge -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type TypeOptions = { - delimiter?: Delimiter; - requireLast?: boolean; -}; -type TypeOptionsWithType = TypeOptions & { - type: string; -}; -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type BaseOptions = { - multiline?: TypeOptions; - singleline?: TypeOptions; -}; -export type Config = BaseOptions & { - overrides?: { - typeLiteral?: BaseOptions; - interface?: BaseOptions; - }; - multilineDetection?: 'brackets' | 'last-member'; -}; -export type Options = [Config]; -export type MessageIds = - | 'expectedComma' - | 'expectedSemi' - | 'unexpectedComma' - | 'unexpectedSemi'; -type LastTokenType = TSESTree.Token; - -interface MakeFixFunctionParams { - optsNone: boolean; - optsSemi: boolean; - lastToken: LastTokenType; - commentsAfterLastToken: LastTokenType | undefined; - missingDelimiter: boolean; - lastTokenLine: string; - isSingleLine: boolean; -} - -type MakeFixFunctionReturnType = - | ((fixer: TSESLint.RuleFixer) => TSESLint.RuleFix) - | null; - -const isLastTokenEndOfLine = (token: LastTokenType, line: string): boolean => { - const positionInLine = token.loc.start.column; - - return positionInLine === line.length - 1; -}; - -const isCommentsEndOfLine = ( - token: LastTokenType, - comments: LastTokenType | undefined, - line: string, -): boolean => { - if (!comments) { - return false; - } - - if (comments.loc.end.line > token.loc.end.line) { - return true; - } - - const positionInLine = comments.loc.end.column; - - return positionInLine === line.length; -}; - -const makeFixFunction = ({ - optsNone, - optsSemi, - lastToken, - commentsAfterLastToken, - missingDelimiter, - lastTokenLine, - isSingleLine, -}: MakeFixFunctionParams): MakeFixFunctionReturnType => { - // if removing is the action but last token is not the end of the line - if ( - optsNone && - !isLastTokenEndOfLine(lastToken, lastTokenLine) && - !isCommentsEndOfLine(lastToken, commentsAfterLastToken, lastTokenLine) && - !isSingleLine - ) { - return null; - } - - return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix => { - if (optsNone) { - // remove the unneeded token - return fixer.remove(lastToken); - } - - const token = optsSemi ? ';' : ','; - - if (missingDelimiter) { - // add the missing delimiter - return fixer.insertTextAfter(lastToken, token); - } - - // correct the current delimiter - return fixer.replaceText(lastToken, token); - }; -}; - -const BASE_SCHEMA: JSONSchema4 = { - type: 'object', - properties: { - multiline: { - type: 'object', - properties: { - delimiter: { $ref: '#/items/0/$defs/multiLineOption' }, - requireLast: { type: 'boolean' }, - }, - additionalProperties: false, - }, - singleline: { - type: 'object', - properties: { - delimiter: { $ref: '#/items/0/$defs/singleLineOption' }, - requireLast: { type: 'boolean' }, - }, - additionalProperties: false, - }, - }, - additionalProperties: false, -}; - -export default createRule({ - name: 'member-delimiter-style', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/member-delimiter-style'], - type: 'layout', - docs: { - description: - 'Require a specific member delimiter style for interfaces and type literals', - }, - fixable: 'whitespace', - messages: { - unexpectedComma: 'Unexpected separator (,).', - unexpectedSemi: 'Unexpected separator (;).', - expectedComma: 'Expected a comma.', - expectedSemi: 'Expected a semicolon.', - }, - schema: [ - { - $defs: { - multiLineOption: { - type: 'string', - enum: ['none', 'semi', 'comma'], - }, - // note can't have "none" for single line delimiter as it's invalid syntax - singleLineOption: { - type: 'string', - enum: ['semi', 'comma'], - }, - // note - need to define this last as it references the enums - delimiterConfig: BASE_SCHEMA, - }, - type: 'object', - properties: { - ...BASE_SCHEMA.properties, - overrides: { - type: 'object', - properties: { - interface: { - $ref: '#/items/0/$defs/delimiterConfig', - }, - typeLiteral: { - $ref: '#/items/0/$defs/delimiterConfig', - }, - }, - additionalProperties: false, - }, - multilineDetection: { - type: 'string', - enum: ['brackets', 'last-member'], - }, - }, - additionalProperties: false, - }, - ], - }, - defaultOptions: [ - { - multiline: { - delimiter: 'semi', - requireLast: true, - }, - singleline: { - delimiter: 'semi', - requireLast: false, - }, - multilineDetection: 'brackets', - }, - ], - create(context, [options]) { - // use the base options as the defaults for the cases - const baseOptions = options; - const overrides = baseOptions.overrides ?? {}; - const interfaceOptions: BaseOptions = deepMerge( - baseOptions, - overrides.interface, - ); - const typeLiteralOptions: BaseOptions = deepMerge( - baseOptions, - overrides.typeLiteral, - ); - - /** - * Check the last token in the given member. - * @param member the member to be evaluated. - * @param opts the options to be validated. - * @param isLast a flag indicating `member` is the last in the interface or type literal. - */ - function checkLastToken( - member: TSESTree.TypeElement, - opts: TypeOptionsWithType, - isLast: boolean, - ): void { - /** - * Resolves the boolean value for the given setting enum value - * @param type the option name - */ - function getOption(type: Delimiter): boolean { - if (isLast && !opts.requireLast) { - // only turn the option on if its expecting no delimiter for the last member - return type === 'none'; - } - return opts.delimiter === type; - } - - let messageId: MessageIds | null = null; - let missingDelimiter = false; - const lastToken = context.sourceCode.getLastToken(member, { - includeComments: false, - }); - - if (!lastToken) { - return; - } - - const commentsAfterLastToken = context.sourceCode - .getCommentsAfter(lastToken) - .pop(); - - const sourceCodeLines = context.sourceCode.getLines(); - const lastTokenLine = sourceCodeLines[lastToken.loc.start.line - 1]; - - const optsSemi = getOption('semi'); - const optsComma = getOption('comma'); - const optsNone = getOption('none'); - - if (lastToken.value === ';') { - if (optsComma) { - messageId = 'expectedComma'; - } else if (optsNone) { - missingDelimiter = true; - messageId = 'unexpectedSemi'; - } - } else if (lastToken.value === ',') { - if (optsSemi) { - messageId = 'expectedSemi'; - } else if (optsNone) { - missingDelimiter = true; - messageId = 'unexpectedComma'; - } - } else { - if (optsSemi) { - missingDelimiter = true; - messageId = 'expectedSemi'; - } else if (optsComma) { - missingDelimiter = true; - messageId = 'expectedComma'; - } - } - - if (messageId) { - context.report({ - node: lastToken, - loc: { - start: { - line: lastToken.loc.end.line, - column: lastToken.loc.end.column, - }, - end: { - line: lastToken.loc.end.line, - column: lastToken.loc.end.column, - }, - }, - messageId, - fix: makeFixFunction({ - optsNone, - optsSemi, - lastToken, - commentsAfterLastToken, - missingDelimiter, - lastTokenLine, - isSingleLine: opts.type === 'single-line', - }), - }); - } - } - - /** - * Check the member separator being used matches the delimiter. - * @param node the node to be evaluated. - */ - function checkMemberSeparatorStyle( - node: TSESTree.TSInterfaceBody | TSESTree.TSTypeLiteral, - ): void { - const members = - node.type === AST_NODE_TYPES.TSInterfaceBody ? node.body : node.members; - - let isSingleLine = node.loc.start.line === node.loc.end.line; - if ( - options.multilineDetection === 'last-member' && - !isSingleLine && - members.length > 0 - ) { - const lastMember = members[members.length - 1]; - if (lastMember.loc.end.line === node.loc.end.line) { - isSingleLine = true; - } - } - - const typeOpts = - node.type === AST_NODE_TYPES.TSInterfaceBody - ? interfaceOptions - : typeLiteralOptions; - const opts = isSingleLine - ? { ...typeOpts.singleline, type: 'single-line' } - : { ...typeOpts.multiline, type: 'multi-line' }; - - members.forEach((member, index) => { - checkLastToken(member, opts, index === members.length - 1); - }); - } - - return { - TSInterfaceBody: checkMemberSeparatorStyle, - TSTypeLiteral: checkMemberSeparatorStyle, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts deleted file mode 100644 index bd1dde121c3f..000000000000 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ /dev/null @@ -1,308 +0,0 @@ -// any is required to work around manipulating the AST in weird ways -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment */ - -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule, isOpeningParenToken, isTypeAssertion } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('no-extra-parens'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'no-extra-parens', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/no-extra-parens'], - type: 'layout', - docs: { - description: 'Disallow unnecessary parentheses', - extendsBaseRule: true, - }, - fixable: 'code', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: baseRule.meta.messages, - }, - defaultOptions: ['all'], - create(context) { - const rules = baseRule.create(context); - - function binaryExp( - node: TSESTree.BinaryExpression | TSESTree.LogicalExpression, - ): void { - const rule = rules.BinaryExpression as (n: typeof node) => void; - - // makes the rule think it should skip the left or right - const isLeftTypeAssertion = isTypeAssertion(node.left); - const isRightTypeAssertion = isTypeAssertion(node.right); - if (isLeftTypeAssertion && isRightTypeAssertion) { - return; // ignore - } - if (isLeftTypeAssertion) { - return rule({ - ...node, - left: { - ...node.left, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - if (isRightTypeAssertion) { - return rule({ - ...node, - right: { - ...node.right, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - return rule(node); - } - function callExp( - node: TSESTree.CallExpression | TSESTree.NewExpression, - ): void { - const rule = rules.CallExpression as (n: typeof node) => void; - - if (isTypeAssertion(node.callee)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rule({ - ...node, - callee: { - ...node.callee, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - if ( - node.arguments.length === 1 && - // is there any opening parenthesis in type arguments - context.sourceCode.getTokenAfter(node.callee, isOpeningParenToken) !== - context.sourceCode.getTokenBefore( - node.arguments[0], - isOpeningParenToken, - ) - ) { - return rule({ - ...node, - arguments: [ - { - ...node.arguments[0], - type: AST_NODE_TYPES.SequenceExpression as any, - }, - ], - }); - } - - return rule(node); - } - function unaryUpdateExpression( - node: TSESTree.UnaryExpression | TSESTree.UpdateExpression, - ): void { - const rule = rules.UnaryExpression as (n: typeof node) => void; - - if (isTypeAssertion(node.argument)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rule({ - ...node, - argument: { - ...node.argument, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - return rule(node); - } - - const overrides: TSESLint.RuleListener = { - // ArrayExpression - ArrowFunctionExpression(node) { - if (!isTypeAssertion(node.body)) { - return rules.ArrowFunctionExpression(node); - } - }, - // AssignmentExpression - AwaitExpression(node) { - if (isTypeAssertion(node.argument)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rules.AwaitExpression({ - ...node, - argument: { - ...node.argument, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - return rules.AwaitExpression(node); - }, - BinaryExpression: binaryExp, - CallExpression: callExp, - ClassDeclaration(node) { - if (node.superClass?.type === AST_NODE_TYPES.TSAsExpression) { - return rules.ClassDeclaration({ - ...node, - superClass: { - ...node.superClass, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - return rules.ClassDeclaration(node); - }, - ClassExpression(node) { - if (node.superClass?.type === AST_NODE_TYPES.TSAsExpression) { - return rules.ClassExpression({ - ...node, - superClass: { - ...node.superClass, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - return rules.ClassExpression(node); - }, - ConditionalExpression(node) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - if (isTypeAssertion(node.test)) { - return rules.ConditionalExpression({ - ...node, - test: { - ...node.test, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - if (isTypeAssertion(node.consequent)) { - return rules.ConditionalExpression({ - ...node, - consequent: { - ...node.consequent, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - if (isTypeAssertion(node.alternate)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rules.ConditionalExpression({ - ...node, - alternate: { - ...node.alternate, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - return rules.ConditionalExpression(node); - }, - ForInStatement(node): void { - if (isTypeAssertion(node.right)) { - // as of 7.20.0 there's no way to skip checking the right of the ForIn - // so just don't validate it at all - return; - } - - return rules.ForInStatement(node); - }, - ForOfStatement(node): void { - if (isTypeAssertion(node.right)) { - // makes the rule skip checking of the right - return rules.ForOfStatement({ - ...node, - type: AST_NODE_TYPES.ForOfStatement, - right: { - ...node.right, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - return rules.ForOfStatement(node); - }, - // DoWhileStatement - ForStatement(node) { - // make the rule skip the piece by removing it entirely - if (node.init && isTypeAssertion(node.init)) { - return rules.ForStatement({ - ...node, - init: null, - }); - } - if (node.test && isTypeAssertion(node.test)) { - return rules.ForStatement({ - ...node, - test: null, - }); - } - if (node.update && isTypeAssertion(node.update)) { - return rules.ForStatement({ - ...node, - update: null, - }); - } - - return rules.ForStatement(node); - }, - 'ForStatement > *.init:exit'(node: TSESTree.Node) { - if (!isTypeAssertion(node)) { - return rules['ForStatement > *.init:exit'](node); - } - }, - // IfStatement - LogicalExpression: binaryExp, - MemberExpression(node) { - if (isTypeAssertion(node.object)) { - // reduces the precedence of the node so the rule thinks it needs to be wrapped - return rules.MemberExpression({ - ...node, - object: { - ...node.object, - type: AST_NODE_TYPES.SequenceExpression as any, - }, - }); - } - - return rules.MemberExpression(node); - }, - NewExpression: callExp, - // ObjectExpression - // ReturnStatement - // SequenceExpression - SpreadElement(node) { - if (!isTypeAssertion(node.argument)) { - return rules.SpreadElement(node); - } - }, - SwitchCase(node) { - if (node.test && !isTypeAssertion(node.test)) { - return rules.SwitchCase(node); - } - }, - // SwitchStatement - ThrowStatement(node) { - if (node.argument && !isTypeAssertion(node.argument)) { - return rules.ThrowStatement(node); - } - }, - UnaryExpression: unaryUpdateExpression, - UpdateExpression: unaryUpdateExpression, - // VariableDeclarator - // WhileStatement - // WithStatement - i'm not going to even bother implementing this terrible and never used feature - YieldExpression(node) { - if (node.argument && !isTypeAssertion(node.argument)) { - return rules.YieldExpression(node); - } - }, - }; - return Object.assign({}, rules, overrides); - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-extra-semi.ts b/packages/eslint-plugin/src/rules/no-extra-semi.ts deleted file mode 100644 index d384cde41fc8..000000000000 --- a/packages/eslint-plugin/src/rules/no-extra-semi.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { - InferMessageIdsTypeFromRule, - InferOptionsTypeFromRule, -} from '../util'; -import { createRule } from '../util'; -import { getESLintCoreRule } from '../util/getESLintCoreRule'; - -const baseRule = getESLintCoreRule('no-extra-semi'); - -export type Options = InferOptionsTypeFromRule; -export type MessageIds = InferMessageIdsTypeFromRule; - -export default createRule({ - name: 'no-extra-semi', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/no-extra-semi'], - type: 'suggestion', - docs: { - description: 'Disallow unnecessary semicolons', - extendsBaseRule: true, - }, - fixable: 'code', - hasSuggestions: baseRule.meta.hasSuggestions, - schema: baseRule.meta.schema, - messages: baseRule.meta.messages, - }, - defaultOptions: [], - create(context) { - const rules = baseRule.create(context); - - return { - ...rules, - 'TSAbstractMethodDefinition, TSAbstractPropertyDefinition'( - node: never, - ): void { - rules['MethodDefinition, PropertyDefinition, StaticBlock'](node); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-throw-literal.ts b/packages/eslint-plugin/src/rules/no-throw-literal.ts deleted file mode 100644 index 2318c75b742e..000000000000 --- a/packages/eslint-plugin/src/rules/no-throw-literal.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; - -import { - createRule, - getParserServices, - isErrorLike, - isTypeAnyType, - isTypeUnknownType, -} from '../util'; - -export type MessageIds = 'object' | 'undef'; - -export type Options = [ - { - allowThrowingAny?: boolean; - allowThrowingUnknown?: boolean; - }, -]; - -export default createRule({ - name: 'no-throw-literal', - meta: { - type: 'problem', - deprecated: true, - replacedBy: ['@typescript-eslint/only-throw-error'], - docs: { - description: 'Disallow throwing literals as exceptions', - extendsBaseRule: true, - requiresTypeChecking: true, - }, - schema: [ - { - type: 'object', - properties: { - allowThrowingAny: { - type: 'boolean', - }, - allowThrowingUnknown: { - type: 'boolean', - }, - }, - additionalProperties: false, - }, - ], - messages: { - object: 'Expected an error object to be thrown.', - undef: 'Do not throw undefined.', - }, - }, - defaultOptions: [ - { - allowThrowingAny: true, - allowThrowingUnknown: true, - }, - ], - create(context, [options]) { - const services = getParserServices(context); - - function checkThrowArgument(node: TSESTree.Node): void { - if ( - node.type === AST_NODE_TYPES.AwaitExpression || - node.type === AST_NODE_TYPES.YieldExpression - ) { - return; - } - - const type = services.getTypeAtLocation(node); - - if (type.flags & ts.TypeFlags.Undefined) { - context.report({ node, messageId: 'undef' }); - return; - } - - if (options.allowThrowingAny && isTypeAnyType(type)) { - return; - } - - if (options.allowThrowingUnknown && isTypeUnknownType(type)) { - return; - } - - if (isErrorLike(services.program, type)) { - return; - } - - context.report({ node, messageId: 'object' }); - } - - return { - ThrowStatement(node): void { - if (node.argument) { - checkThrowArgument(node.argument); - } - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts deleted file mode 100644 index 3dd793e79831..000000000000 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; - -import { - createRule, - getConstrainedTypeAtLocation, - getParserServices, - getStaticStringValue, - isTypeFlagSet, - isUndefinedIdentifier, -} from '../util'; - -export type MessageId = 'noUnnecessaryTemplateExpression'; - -export default createRule<[], MessageId>({ - name: 'no-useless-template-literals', - meta: { - fixable: 'code', - type: 'suggestion', - docs: { - description: 'Disallow unnecessary template expressions', - requiresTypeChecking: true, - }, - messages: { - noUnnecessaryTemplateExpression: - 'Template literal expression is unnecessary and can be simplified.', - }, - schema: [], - deprecated: true, - replacedBy: ['@typescript-eslint/no-unnecessary-template-expression'], - }, - defaultOptions: [], - create(context) { - const services = getParserServices(context); - - function isUnderlyingTypeString( - expression: TSESTree.Expression, - ): expression is TSESTree.StringLiteral | TSESTree.Identifier { - const type = getConstrainedTypeAtLocation(services, expression); - - const isString = (t: ts.Type): boolean => { - return isTypeFlagSet(t, ts.TypeFlags.StringLike); - }; - - if (type.isUnion()) { - return type.types.every(isString); - } - - if (type.isIntersection()) { - return type.types.some(isString); - } - - return isString(type); - } - - function isLiteral(expression: TSESTree.Expression): boolean { - return expression.type === AST_NODE_TYPES.Literal; - } - - function isTemplateLiteral(expression: TSESTree.Expression): boolean { - return expression.type === AST_NODE_TYPES.TemplateLiteral; - } - - function isInfinityIdentifier(expression: TSESTree.Expression): boolean { - return ( - expression.type === AST_NODE_TYPES.Identifier && - expression.name === 'Infinity' - ); - } - - function isNaNIdentifier(expression: TSESTree.Expression): boolean { - return ( - expression.type === AST_NODE_TYPES.Identifier && - expression.name === 'NaN' - ); - } - - return { - TemplateLiteral(node: TSESTree.TemplateLiteral): void { - if (node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) { - return; - } - - const hasSingleStringVariable = - node.quasis.length === 2 && - node.quasis[0].value.raw === '' && - node.quasis[1].value.raw === '' && - node.expressions.length === 1 && - isUnderlyingTypeString(node.expressions[0]); - - if (hasSingleStringVariable) { - context.report({ - node: node.expressions[0], - messageId: 'noUnnecessaryTemplateExpression', - fix(fixer): TSESLint.RuleFix[] { - const [prevQuasi, nextQuasi] = node.quasis; - - // Remove the quasis and backticks. - return [ - fixer.removeRange([ - prevQuasi.range[1] - 3, - node.expressions[0].range[0], - ]), - - fixer.removeRange([ - node.expressions[0].range[1], - nextQuasi.range[0] + 2, - ]), - ]; - }, - }); - - return; - } - - const fixableExpressions = node.expressions.filter( - expression => - isLiteral(expression) || - isTemplateLiteral(expression) || - isUndefinedIdentifier(expression) || - isInfinityIdentifier(expression) || - isNaNIdentifier(expression), - ); - - fixableExpressions.forEach(expression => { - context.report({ - node: expression, - messageId: 'noUnnecessaryTemplateExpression', - fix(fixer): TSESLint.RuleFix[] { - const index = node.expressions.indexOf(expression); - const prevQuasi = node.quasis[index]; - const nextQuasi = node.quasis[index + 1]; - - // Remove the quasis' parts that are related to the current expression. - const fixes = [ - fixer.removeRange([ - prevQuasi.range[1] - 2, - expression.range[0], - ]), - - fixer.removeRange([ - expression.range[1], - nextQuasi.range[0] + 1, - ]), - ]; - - const stringValue = getStaticStringValue(expression); - - if (stringValue != null) { - const escapedValue = stringValue.replace(/([`$\\])/g, '\\$1'); - - fixes.push(fixer.replaceText(expression, escapedValue)); - } else if (isTemplateLiteral(expression)) { - // Note that some template literals get handled in the previous branch too. - // Remove the beginning and trailing backtick characters. - fixes.push( - fixer.removeRange([ - expression.range[0], - expression.range[0] + 1, - ]), - fixer.removeRange([ - expression.range[1] - 1, - expression.range[1], - ]), - ); - } - - return fixes; - }, - }); - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts b/packages/eslint-plugin/src/rules/padding-line-between-statements.ts deleted file mode 100644 index be4227903475..000000000000 --- a/packages/eslint-plugin/src/rules/padding-line-between-statements.ts +++ /dev/null @@ -1,825 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion, eslint-plugin/no-property-in-node */ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import { - createRule, - isClosingBraceToken, - isFunction, - isNotSemicolonToken, - isParenthesized, - isSemicolonToken, - isTokenOnSameLine, -} from '../util'; - -/** - * This rule is a replica of padding-line-between-statements. - * - * Ideally we would want to extend the rule support typescript specific support. - * But since not all the state is exposed by the eslint and eslint has frozen stylistic rules, - * (see - https://eslint.org/blog/2020/05/changes-to-rules-policies for details.) - * we are forced to re-implement the rule here. - * - * We have tried to keep the implementation as close as possible to the eslint implementation, to make - * patching easier for future contributors. - * - * Reference rule - https://github.com/eslint/eslint/blob/main/lib/rules/padding-line-between-statements.js - */ - -type NodeTest = ( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -) => boolean; - -interface NodeTestObject { - test: NodeTest; -} - -export interface PaddingOption { - blankLine: keyof typeof PaddingTypes; - prev: string[] | string; - next: string[] | string; -} - -export type MessageIds = 'expectedBlankLine' | 'unexpectedBlankLine'; -export type Options = PaddingOption[]; - -const LT = `[${Array.from( - new Set(['\r\n', '\r', '\n', '\u2028', '\u2029']), -).join('')}]`; -const PADDING_LINE_SEQUENCE = new RegExp( - String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, - 'u', -); - -/** - * Creates tester which check if a node starts with specific keyword with the - * appropriate AST_NODE_TYPES. - * @param keyword The keyword to test. - * @returns the created tester. - * @private - */ -function newKeywordTester( - type: AST_NODE_TYPES | AST_NODE_TYPES[], - keyword: string, -): NodeTestObject { - return { - test(node, sourceCode): boolean { - const isSameKeyword = sourceCode.getFirstToken(node)?.value === keyword; - const isSameType = Array.isArray(type) - ? type.some(val => val === node.type) - : type === node.type; - - return isSameKeyword && isSameType; - }, - }; -} - -/** - * Creates tester which check if a node starts with specific keyword and spans a single line. - * @param keyword The keyword to test. - * @returns the created tester. - * @private - */ -function newSinglelineKeywordTester(keyword: string): NodeTestObject { - return { - test(node, sourceCode): boolean { - return ( - node.loc.start.line === node.loc.end.line && - sourceCode.getFirstToken(node)!.value === keyword - ); - }, - }; -} - -/** - * Creates tester which check if a node starts with specific keyword and spans multiple lines. - * @param keyword The keyword to test. - * @returns the created tester. - * @private - */ -function newMultilineKeywordTester(keyword: string): NodeTestObject { - return { - test(node, sourceCode): boolean { - return ( - node.loc.start.line !== node.loc.end.line && - sourceCode.getFirstToken(node)!.value === keyword - ); - }, - }; -} - -/** - * Creates tester which check if a node is specific type. - * @param type The node type to test. - * @returns the created tester. - * @private - */ -function newNodeTypeTester(type: AST_NODE_TYPES): NodeTestObject { - return { - test: (node): boolean => node.type === type, - }; -} - -/** - * Skips a chain expression node - * @param node The node to test - * @returns A non-chain expression - * @private - */ -function skipChainExpression(node: TSESTree.Node): TSESTree.Node { - return node.type === AST_NODE_TYPES.ChainExpression ? node.expression : node; -} - -/** - * Checks the given node is an expression statement of IIFE. - * @param node The node to check. - * @returns `true` if the node is an expression statement of IIFE. - * @private - */ -function isIIFEStatement(node: TSESTree.Node): boolean { - if (node.type === AST_NODE_TYPES.ExpressionStatement) { - let expression = skipChainExpression(node.expression); - if (expression.type === AST_NODE_TYPES.UnaryExpression) { - expression = skipChainExpression(expression.argument); - } - if (expression.type === AST_NODE_TYPES.CallExpression) { - let node: TSESTree.Node = expression.callee; - while (node.type === AST_NODE_TYPES.SequenceExpression) { - node = node.expressions[node.expressions.length - 1]; - } - return isFunction(node); - } - } - return false; -} - -/** - * Checks the given node is a CommonJS require statement - * @param node The node to check. - * @returns `true` if the node is a CommonJS require statement. - * @private - */ -function isCJSRequire(node: TSESTree.Node): boolean { - if (node.type === AST_NODE_TYPES.VariableDeclaration) { - const declaration = node.declarations.at(0); - if (declaration?.init) { - let call = declaration.init; - while (call.type === AST_NODE_TYPES.MemberExpression) { - call = call.object; - } - if ( - call.type === AST_NODE_TYPES.CallExpression && - call.callee.type === AST_NODE_TYPES.Identifier - ) { - return call.callee.name === 'require'; - } - } - } - return false; -} - -/** - * Checks whether the given node is a block-like statement. - * This checks the last token of the node is the closing brace of a block. - * @param node The node to check. - * @param sourceCode The source code to get tokens. - * @returns `true` if the node is a block-like statement. - * @private - */ -function isBlockLikeStatement( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): boolean { - // do-while with a block is a block-like statement. - if ( - node.type === AST_NODE_TYPES.DoWhileStatement && - node.body.type === AST_NODE_TYPES.BlockStatement - ) { - return true; - } - - /** - * IIFE is a block-like statement specially from - * JSCS#disallowPaddingNewLinesAfterBlocks. - */ - if (isIIFEStatement(node)) { - return true; - } - - // Checks the last token is a closing brace of blocks. - const lastToken = sourceCode.getLastToken(node, isNotSemicolonToken); - const belongingNode = - lastToken && isClosingBraceToken(lastToken) - ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) - : null; - - return ( - !!belongingNode && - (belongingNode.type === AST_NODE_TYPES.BlockStatement || - belongingNode.type === AST_NODE_TYPES.SwitchStatement) - ); -} - -/** - * Check whether the given node is a directive or not. - * @param node The node to check. - * @param sourceCode The source code object to get tokens. - * @returns `true` if the node is a directive. - */ -function isDirective( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): boolean { - return ( - node.type === AST_NODE_TYPES.ExpressionStatement && - (node.parent.type === AST_NODE_TYPES.Program || - (node.parent.type === AST_NODE_TYPES.BlockStatement && - isFunction(node.parent.parent))) && - node.expression.type === AST_NODE_TYPES.Literal && - typeof node.expression.value === 'string' && - !isParenthesized(node.expression, sourceCode) - ); -} - -/** - * Check whether the given node is a part of directive prologue or not. - * @param node The node to check. - * @param sourceCode The source code object to get tokens. - * @returns `true` if the node is a part of directive prologue. - */ -function isDirectivePrologue( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): boolean { - if ( - isDirective(node, sourceCode) && - node.parent && - 'body' in node.parent && - Array.isArray(node.parent.body) - ) { - for (const sibling of node.parent.body) { - if (sibling === node) { - break; - } - if (!isDirective(sibling, sourceCode)) { - return false; - } - } - return true; - } - return false; -} - -/** - * Checks the given node is a CommonJS export statement - * @param node The node to check. - * @returns `true` if the node is a CommonJS export statement. - * @private - */ -function isCJSExport(node: TSESTree.Node): boolean { - if (node.type === AST_NODE_TYPES.ExpressionStatement) { - const expression = node.expression; - if (expression.type === AST_NODE_TYPES.AssignmentExpression) { - let left = expression.left; - if (left.type === AST_NODE_TYPES.MemberExpression) { - while (left.object.type === AST_NODE_TYPES.MemberExpression) { - left = left.object; - } - return ( - left.object.type === AST_NODE_TYPES.Identifier && - (left.object.name === 'exports' || - (left.object.name === 'module' && - left.property.type === AST_NODE_TYPES.Identifier && - left.property.name === 'exports')) - ); - } - } - } - return false; -} - -/** - * Check whether the given node is an expression - * @param node The node to check. - * @param sourceCode The source code object to get tokens. - * @returns `true` if the node is an expression - */ -function isExpression( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): boolean { - return ( - node.type === AST_NODE_TYPES.ExpressionStatement && - !isDirectivePrologue(node, sourceCode) - ); -} - -/** - * Gets the actual last token. - * - * If a semicolon is semicolon-less style's semicolon, this ignores it. - * For example: - * - * foo() - * ;[1, 2, 3].forEach(bar) - * @param node The node to get. - * @param sourceCode The source code to get tokens. - * @private - */ -function getActualLastToken( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, -): TSESTree.Token | null { - const semiToken = sourceCode.getLastToken(node)!; - const prevToken = sourceCode.getTokenBefore(semiToken); - const nextToken = sourceCode.getTokenAfter(semiToken); - const isSemicolonLessStyle = - prevToken && - nextToken && - prevToken.range[0] >= node.range[0] && - isSemicolonToken(semiToken) && - semiToken.loc.start.line !== prevToken.loc.end.line && - semiToken.loc.end.line === nextToken.loc.start.line; - - return isSemicolonLessStyle ? prevToken : semiToken; -} - -/** - * This returns the concatenation of the first 2 captured strings. - * @param _ Unused. Whole matched string. - * @param trailingSpaces The trailing spaces of the first line. - * @param indentSpaces The indentation spaces of the last line. - * @returns The concatenation of trailingSpaces and indentSpaces. - * @private - */ -function replacerToRemovePaddingLines( - _: string, - trailingSpaces: string, - indentSpaces: string, -): string { - return trailingSpaces + indentSpaces; -} - -/** - * Check and report statements for `any` configuration. - * It does nothing. - * - * @private - */ -function verifyForAny(): void { - // Empty -} - -/** - * Check and report statements for `never` configuration. - * This autofix removes blank lines between the given 2 statements. - * However, if comments exist between 2 blank lines, it does not remove those - * blank lines automatically. - * @param context The rule context to report. - * @param _ Unused. The previous node to check. - * @param nextNode The next node to check. - * @param paddingLines The array of token pairs that blank - * lines exist between the pair. - * - * @private - */ -function verifyForNever( - context: TSESLint.RuleContext, - _: TSESTree.Node, - nextNode: TSESTree.Node, - paddingLines: [TSESTree.Token, TSESTree.Token][], -): void { - if (paddingLines.length === 0) { - return; - } - - context.report({ - node: nextNode, - messageId: 'unexpectedBlankLine', - fix(fixer) { - if (paddingLines.length >= 2) { - return null; - } - - const prevToken = paddingLines[0][0]; - const nextToken = paddingLines[0][1]; - const start = prevToken.range[1]; - const end = nextToken.range[0]; - const text = context.sourceCode.text - .slice(start, end) - .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); - - return fixer.replaceTextRange([start, end], text); - }, - }); -} - -/** - * Check and report statements for `always` configuration. - * This autofix inserts a blank line between the given 2 statements. - * If the `prevNode` has trailing comments, it inserts a blank line after the - * trailing comments. - * @param context The rule context to report. - * @param prevNode The previous node to check. - * @param nextNode The next node to check. - * @param paddingLines The array of token pairs that blank - * lines exist between the pair. - * - * @private - */ -function verifyForAlways( - context: TSESLint.RuleContext, - prevNode: TSESTree.Node, - nextNode: TSESTree.Node, - paddingLines: [TSESTree.Token, TSESTree.Token][], -): void { - if (paddingLines.length > 0) { - return; - } - - context.report({ - node: nextNode, - messageId: 'expectedBlankLine', - fix(fixer) { - let prevToken = getActualLastToken(prevNode, context.sourceCode)!; - const nextToken = - context.sourceCode.getFirstTokenBetween(prevToken, nextNode, { - includeComments: true, - - /** - * Skip the trailing comments of the previous node. - * This inserts a blank line after the last trailing comment. - * - * For example: - * - * foo(); // trailing comment. - * // comment. - * bar(); - * - * Get fixed to: - * - * foo(); // trailing comment. - * - * // comment. - * bar(); - * @param token The token to check. - * @returns `true` if the token is not a trailing comment. - * @private - */ - filter(token) { - if (isTokenOnSameLine(prevToken, token)) { - prevToken = token; - return false; - } - return true; - }, - }) ?? nextNode; - const insertText = isTokenOnSameLine(prevToken, nextToken) - ? '\n\n' - : '\n'; - - return fixer.insertTextAfter(prevToken, insertText); - }, - }); -} - -/** - * Types of blank lines. - * `any`, `never`, and `always` are defined. - * Those have `verify` method to check and report statements. - * @private - */ -const PaddingTypes = { - any: { verify: verifyForAny }, - never: { verify: verifyForNever }, - always: { verify: verifyForAlways }, -}; - -/** - * Types of statements. - * Those have `test` method to check it matches to the given statement. - * @private - */ -const StatementTypes: Record = { - '*': { test: (): boolean => true }, - 'block-like': { test: isBlockLikeStatement }, - exports: { test: isCJSExport }, - require: { test: isCJSRequire }, - directive: { test: isDirectivePrologue }, - expression: { test: isExpression }, - iife: { test: isIIFEStatement }, - - 'multiline-block-like': { - test: (node, sourceCode) => - node.loc.start.line !== node.loc.end.line && - isBlockLikeStatement(node, sourceCode), - }, - 'multiline-expression': { - test: (node, sourceCode) => - node.loc.start.line !== node.loc.end.line && - node.type === AST_NODE_TYPES.ExpressionStatement && - !isDirectivePrologue(node, sourceCode), - }, - - 'multiline-const': newMultilineKeywordTester('const'), - 'multiline-let': newMultilineKeywordTester('let'), - 'multiline-var': newMultilineKeywordTester('var'), - 'singleline-const': newSinglelineKeywordTester('const'), - 'singleline-let': newSinglelineKeywordTester('let'), - 'singleline-var': newSinglelineKeywordTester('var'), - - block: newNodeTypeTester(AST_NODE_TYPES.BlockStatement), - empty: newNodeTypeTester(AST_NODE_TYPES.EmptyStatement), - function: newNodeTypeTester(AST_NODE_TYPES.FunctionDeclaration), - - break: newKeywordTester(AST_NODE_TYPES.BreakStatement, 'break'), - case: newKeywordTester(AST_NODE_TYPES.SwitchCase, 'case'), - class: newKeywordTester(AST_NODE_TYPES.ClassDeclaration, 'class'), - const: newKeywordTester(AST_NODE_TYPES.VariableDeclaration, 'const'), - continue: newKeywordTester(AST_NODE_TYPES.ContinueStatement, 'continue'), - debugger: newKeywordTester(AST_NODE_TYPES.DebuggerStatement, 'debugger'), - default: newKeywordTester( - [AST_NODE_TYPES.SwitchCase, AST_NODE_TYPES.ExportDefaultDeclaration], - 'default', - ), - do: newKeywordTester(AST_NODE_TYPES.DoWhileStatement, 'do'), - export: newKeywordTester( - [ - AST_NODE_TYPES.ExportDefaultDeclaration, - AST_NODE_TYPES.ExportNamedDeclaration, - ], - 'export', - ), - for: newKeywordTester( - [ - AST_NODE_TYPES.ForStatement, - AST_NODE_TYPES.ForInStatement, - AST_NODE_TYPES.ForOfStatement, - ], - 'for', - ), - if: newKeywordTester(AST_NODE_TYPES.IfStatement, 'if'), - import: newKeywordTester(AST_NODE_TYPES.ImportDeclaration, 'import'), - let: newKeywordTester(AST_NODE_TYPES.VariableDeclaration, 'let'), - return: newKeywordTester(AST_NODE_TYPES.ReturnStatement, 'return'), - switch: newKeywordTester(AST_NODE_TYPES.SwitchStatement, 'switch'), - throw: newKeywordTester(AST_NODE_TYPES.ThrowStatement, 'throw'), - try: newKeywordTester(AST_NODE_TYPES.TryStatement, 'try'), - var: newKeywordTester(AST_NODE_TYPES.VariableDeclaration, 'var'), - while: newKeywordTester( - [AST_NODE_TYPES.WhileStatement, AST_NODE_TYPES.DoWhileStatement], - 'while', - ), - with: newKeywordTester(AST_NODE_TYPES.WithStatement, 'with'), - - // Additional Typescript constructs - interface: newKeywordTester( - AST_NODE_TYPES.TSInterfaceDeclaration, - 'interface', - ), - type: newKeywordTester(AST_NODE_TYPES.TSTypeAliasDeclaration, 'type'), -}; - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -export default createRule({ - name: 'padding-line-between-statements', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/padding-line-between-statements'], - type: 'layout', - docs: { - description: 'Require or disallow padding lines between statements', - extendsBaseRule: true, - }, - fixable: 'whitespace', - hasSuggestions: false, - // This is intentionally an array schema as you can pass 0..n config objects - schema: { - $defs: { - paddingType: { - type: 'string', - enum: Object.keys(PaddingTypes), - }, - statementType: { - anyOf: [ - { - type: 'string', - enum: Object.keys(StatementTypes), - }, - { - type: 'array', - items: { - type: 'string', - enum: Object.keys(StatementTypes), - }, - minItems: 1, - uniqueItems: true, - additionalItems: false, - }, - ], - }, - }, - type: 'array', - additionalItems: false, - items: { - type: 'object', - properties: { - blankLine: { $ref: '#/$defs/paddingType' }, - prev: { $ref: '#/$defs/statementType' }, - next: { $ref: '#/$defs/statementType' }, - }, - additionalProperties: false, - required: ['blankLine', 'prev', 'next'], - }, - }, - messages: { - unexpectedBlankLine: 'Unexpected blank line before this statement.', - expectedBlankLine: 'Expected blank line before this statement.', - }, - }, - defaultOptions: [], - create(context) { - // eslint-disable-next-line no-restricted-syntax -- We need all raw options. - const configureList = context.options; - - type Scope = { - upper: Scope; - prevNode: TSESTree.Node | null; - } | null; - - let scopeInfo: Scope = null; - - /** - * Processes to enter to new scope. - * This manages the current previous statement. - * - * @private - */ - function enterScope(): void { - scopeInfo = { - upper: scopeInfo, - prevNode: null, - }; - } - - /** - * Processes to exit from the current scope. - * - * @private - */ - function exitScope(): void { - if (scopeInfo) { - scopeInfo = scopeInfo.upper; - } - } - - /** - * Checks whether the given node matches the given type. - * @param node The statement node to check. - * @param type The statement type to check. - * @returns `true` if the statement node matched the type. - * @private - */ - function match(node: TSESTree.Node, type: string[] | string): boolean { - let innerStatementNode = node; - - while (innerStatementNode.type === AST_NODE_TYPES.LabeledStatement) { - innerStatementNode = innerStatementNode.body; - } - - if (Array.isArray(type)) { - return type.some(match.bind(null, innerStatementNode)); - } - - return StatementTypes[type].test(innerStatementNode, context.sourceCode); - } - - /** - * Finds the last matched configure from configureList. - * @paramprevNode The previous statement to match. - * @paramnextNode The current statement to match. - * @returns The tester of the last matched configure. - * @private - */ - function getPaddingType( - prevNode: TSESTree.Node, - nextNode: TSESTree.Node, - ): (typeof PaddingTypes)[keyof typeof PaddingTypes] { - for (let i = configureList.length - 1; i >= 0; --i) { - const configure = configureList[i]; - if ( - match(prevNode, configure.prev) && - match(nextNode, configure.next) - ) { - return PaddingTypes[configure.blankLine]; - } - } - return PaddingTypes.any; - } - - /** - * Gets padding line sequences between the given 2 statements. - * Comments are separators of the padding line sequences. - * @paramprevNode The previous statement to count. - * @paramnextNode The current statement to count. - * @returns The array of token pairs. - * @private - */ - function getPaddingLineSequences( - prevNode: TSESTree.Node, - nextNode: TSESTree.Node, - ): [TSESTree.Token, TSESTree.Token][] { - const pairs: [TSESTree.Token, TSESTree.Token][] = []; - let prevToken: TSESTree.Token = getActualLastToken( - prevNode, - context.sourceCode, - )!; - - if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { - do { - const token: TSESTree.Token = context.sourceCode.getTokenAfter( - prevToken, - { - includeComments: true, - }, - )!; - - if (token.loc.start.line - prevToken.loc.end.line >= 2) { - pairs.push([prevToken, token]); - } - prevToken = token; - } while (prevToken.range[0] < nextNode.range[0]); - } - - return pairs; - } - - /** - * Verify padding lines between the given node and the previous node. - * @param node The node to verify. - * - * @private - */ - function verify(node: TSESTree.Node): void { - if ( - !node.parent || - ![ - AST_NODE_TYPES.BlockStatement, - AST_NODE_TYPES.Program, - AST_NODE_TYPES.SwitchCase, - AST_NODE_TYPES.SwitchStatement, - AST_NODE_TYPES.TSModuleBlock, - ].includes(node.parent.type) - ) { - return; - } - - // Save this node as the current previous statement. - const prevNode = scopeInfo!.prevNode; - - // Verify. - if (prevNode) { - const type = getPaddingType(prevNode, node); - const paddingLines = getPaddingLineSequences(prevNode, node); - - type.verify(context, prevNode, node, paddingLines); - } - - scopeInfo!.prevNode = node; - } - - /** - * Verify padding lines between the given node and the previous node. - * Then process to enter to new scope. - * @param node The node to verify. - * - * @private - */ - function verifyThenEnterScope(node: TSESTree.Node): void { - verify(node); - enterScope(); - } - - return { - Program: enterScope, - BlockStatement: enterScope, - SwitchStatement: enterScope, - TSModuleBlock: enterScope, - 'Program:exit': exitScope, - 'BlockStatement:exit': exitScope, - 'SwitchStatement:exit': exitScope, - 'TSModuleBlock:exit': exitScope, - - ':statement': verify, - - SwitchCase: verifyThenEnterScope, - TSDeclareFunction: verifyThenEnterScope, - 'SwitchCase:exit': exitScope, - 'TSDeclareFunction:exit': exitScope, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/space-before-function-paren.ts b/packages/eslint-plugin/src/rules/space-before-function-paren.ts deleted file mode 100644 index 3d8c813ccc46..000000000000 --- a/packages/eslint-plugin/src/rules/space-before-function-paren.ts +++ /dev/null @@ -1,196 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; - -import { createRule, isOpeningParenToken } from '../util'; - -export type Option = 'always' | 'never'; -export type FuncOption = Option | 'ignore'; - -export type Options = [ - | Option - | { - anonymous?: FuncOption; - named?: FuncOption; - asyncArrow?: FuncOption; - }, -]; -export type MessageIds = 'missing' | 'unexpected'; - -export default createRule({ - name: 'space-before-function-paren', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/space-before-function-paren'], - type: 'layout', - docs: { - description: 'Enforce consistent spacing before function parenthesis', - extendsBaseRule: true, - }, - fixable: 'whitespace', - schema: [ - { - oneOf: [ - { - type: 'string', - enum: ['always', 'never'], - }, - { - type: 'object', - properties: { - anonymous: { - type: 'string', - enum: ['always', 'never', 'ignore'], - }, - named: { - type: 'string', - enum: ['always', 'never', 'ignore'], - }, - asyncArrow: { - type: 'string', - enum: ['always', 'never', 'ignore'], - }, - }, - additionalProperties: false, - }, - ], - }, - ], - messages: { - unexpected: 'Unexpected space before function parentheses.', - missing: 'Missing space before function parentheses.', - }, - }, - defaultOptions: ['always'], - - create(context, [firstOption]) { - const baseConfig = typeof firstOption === 'string' ? firstOption : 'always'; - const overrideConfig = typeof firstOption === 'object' ? firstOption : {}; - - /** - * Determines whether a function has a name. - */ - function isNamedFunction( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.TSDeclareFunction - | TSESTree.TSEmptyBodyFunctionExpression, - ): boolean { - if (node.id != null) { - return true; - } - - const parent = node.parent; - - return ( - parent.type === AST_NODE_TYPES.MethodDefinition || - parent.type === AST_NODE_TYPES.TSAbstractMethodDefinition || - (parent.type === AST_NODE_TYPES.Property && - (parent.kind === 'get' || parent.kind === 'set' || parent.method)) - ); - } - - /** - * Gets the config for a given function - */ - function getConfigForFunction( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.TSDeclareFunction - | TSESTree.TSEmptyBodyFunctionExpression, - ): FuncOption { - if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { - // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar - if ( - node.async && - isOpeningParenToken( - context.sourceCode.getFirstToken(node, { skip: 1 })!, - ) - ) { - return overrideConfig.asyncArrow ?? baseConfig; - } - } else if (isNamedFunction(node)) { - return overrideConfig.named ?? baseConfig; - - // `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}` - } else if (!node.generator) { - return overrideConfig.anonymous ?? baseConfig; - } - - return 'ignore'; - } - - /** - * Checks the parens of a function node - * @param node A function node - */ - function checkFunction( - node: - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.TSDeclareFunction - | TSESTree.TSEmptyBodyFunctionExpression, - ): void { - const functionConfig = getConfigForFunction(node); - - if (functionConfig === 'ignore') { - return; - } - - let leftToken: TSESTree.Token; - let rightToken: TSESTree.Token; - if (node.typeParameters) { - leftToken = context.sourceCode.getLastToken(node.typeParameters)!; - rightToken = context.sourceCode.getTokenAfter(leftToken)!; - } else { - rightToken = context.sourceCode.getFirstToken( - node, - isOpeningParenToken, - )!; - leftToken = context.sourceCode.getTokenBefore(rightToken)!; - } - - const hasSpacing = context.sourceCode.isSpaceBetween( - leftToken, - rightToken, - ); - - if (hasSpacing && functionConfig === 'never') { - context.report({ - node, - loc: { - start: leftToken.loc.end, - end: rightToken.loc.start, - }, - messageId: 'unexpected', - fix: fixer => - fixer.removeRange([leftToken.range[1], rightToken.range[0]]), - }); - } else if ( - !hasSpacing && - functionConfig === 'always' && - (!node.typeParameters || node.id) - ) { - context.report({ - node, - loc: rightToken.loc, - messageId: 'missing', - fix: fixer => fixer.insertTextAfter(leftToken, ' '), - }); - } - } - - return { - ArrowFunctionExpression: checkFunction, - FunctionDeclaration: checkFunction, - FunctionExpression: checkFunction, - TSEmptyBodyFunctionExpression: checkFunction, - TSDeclareFunction: checkFunction, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts deleted file mode 100644 index 0f0988a3332b..000000000000 --- a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts +++ /dev/null @@ -1,289 +0,0 @@ -import type { TSESTree } from '@typescript-eslint/utils'; - -import { - createRule, - isClassOrTypeElement, - isFunction, - isFunctionOrFunctionType, - isIdentifier, - isTSConstructorType, - isTSFunctionType, - isVariableDeclarator, -} from '../util'; - -interface WhitespaceRule { - readonly before?: boolean; - readonly after?: boolean; -} - -interface WhitespaceOverride { - readonly colon?: WhitespaceRule; - readonly arrow?: WhitespaceRule; - readonly variable?: WhitespaceRule; - readonly property?: WhitespaceRule; - readonly parameter?: WhitespaceRule; - readonly returnType?: WhitespaceRule; -} - -interface Config extends WhitespaceRule { - readonly overrides?: WhitespaceOverride; -} - -type WhitespaceRules = Required; - -export type Options = [Config?]; -export type MessageIds = - | 'expectedSpaceAfter' - | 'expectedSpaceBefore' - | 'unexpectedSpaceAfter' - | 'unexpectedSpaceBefore' - | 'unexpectedSpaceBetween'; - -function createRules(options?: Config): WhitespaceRules { - const globals = { - ...(options?.before !== undefined ? { before: options.before } : {}), - ...(options?.after !== undefined ? { after: options.after } : {}), - }; - const override = options?.overrides ?? {}; - const colon = { - ...{ before: false, after: true }, - ...globals, - ...override.colon, - }; - const arrow = { - ...{ before: true, after: true }, - ...globals, - ...override.arrow, - }; - - return { - colon: colon, - arrow: arrow, - variable: { ...colon, ...override.variable }, - property: { ...colon, ...override.property }, - parameter: { ...colon, ...override.parameter }, - returnType: { ...colon, ...override.returnType }, - }; -} - -function getIdentifierRules( - rules: WhitespaceRules, - node: TSESTree.Node | undefined, -): WhitespaceRule { - const scope = node?.parent; - - if (isVariableDeclarator(scope)) { - return rules.variable; - } else if (isFunctionOrFunctionType(scope)) { - return rules.parameter; - } - return rules.colon; -} - -function getRules( - rules: WhitespaceRules, - node: TSESTree.TypeNode, -): WhitespaceRule { - const scope = node.parent.parent; - - if (isTSFunctionType(scope) || isTSConstructorType(scope)) { - return rules.arrow; - } else if (isIdentifier(scope)) { - return getIdentifierRules(rules, scope); - } else if (isClassOrTypeElement(scope)) { - return rules.property; - } else if (isFunction(scope)) { - return rules.returnType; - } - return rules.colon; -} - -export default createRule({ - name: 'type-annotation-spacing', - meta: { - deprecated: true, - replacedBy: ['@stylistic/ts/type-annotation-spacing'], - type: 'layout', - docs: { - description: 'Require consistent spacing around type annotations', - }, - fixable: 'whitespace', - messages: { - expectedSpaceAfter: "Expected a space after the '{{type}}'.", - expectedSpaceBefore: "Expected a space before the '{{type}}'.", - unexpectedSpaceAfter: "Unexpected space after the '{{type}}'.", - unexpectedSpaceBefore: "Unexpected space before the '{{type}}'.", - unexpectedSpaceBetween: - "Unexpected space between the '{{previousToken}}' and the '{{type}}'.", - }, - schema: [ - { - $defs: { - spacingConfig: { - type: 'object', - properties: { - before: { type: 'boolean' }, - after: { type: 'boolean' }, - }, - additionalProperties: false, - }, - }, - type: 'object', - properties: { - before: { type: 'boolean' }, - after: { type: 'boolean' }, - overrides: { - type: 'object', - properties: { - colon: { $ref: '#/items/0/$defs/spacingConfig' }, - arrow: { $ref: '#/items/0/$defs/spacingConfig' }, - variable: { $ref: '#/items/0/$defs/spacingConfig' }, - parameter: { $ref: '#/items/0/$defs/spacingConfig' }, - property: { $ref: '#/items/0/$defs/spacingConfig' }, - returnType: { $ref: '#/items/0/$defs/spacingConfig' }, - }, - additionalProperties: false, - }, - }, - additionalProperties: false, - }, - ], - }, - defaultOptions: [ - // technically there is a default, but the overrides mean - // that if we apply them here, it will break the no override case. - {}, - ], - create(context, [options]) { - const punctuators = [':', '=>']; - - const ruleSet = createRules(options); - - /** - * Checks if there's proper spacing around type annotations (no space - * before colon, one space after). - */ - function checkTypeAnnotationSpacing( - typeAnnotation: TSESTree.TypeNode, - ): void { - const nextToken = typeAnnotation; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const punctuatorTokenEnd = context.sourceCode.getTokenBefore(nextToken)!; - let punctuatorTokenStart = punctuatorTokenEnd; - let previousToken = - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - context.sourceCode.getTokenBefore(punctuatorTokenEnd)!; - let type = punctuatorTokenEnd.value; - - if (!punctuators.includes(type)) { - return; - } - - const { before, after } = getRules(ruleSet, typeAnnotation); - - if (type === ':' && previousToken.value === '?') { - if ( - context.sourceCode.isSpaceBetween(previousToken, punctuatorTokenStart) - ) { - context.report({ - node: punctuatorTokenStart, - messageId: 'unexpectedSpaceBetween', - data: { - type, - previousToken: previousToken.value, - }, - fix(fixer) { - return fixer.removeRange([ - previousToken.range[1], - punctuatorTokenStart.range[0], - ]); - }, - }); - } - - // shift the start to the ? - type = '?:'; - punctuatorTokenStart = previousToken; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - previousToken = context.sourceCode.getTokenBefore(previousToken)!; - - // handle the +/- modifiers for optional modification operators - if (previousToken.value === '+' || previousToken.value === '-') { - type = `${previousToken.value}?:`; - punctuatorTokenStart = previousToken; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - previousToken = context.sourceCode.getTokenBefore(previousToken)!; - } - } - - const previousDelta = - punctuatorTokenStart.range[0] - previousToken.range[1]; - const nextDelta = nextToken.range[0] - punctuatorTokenEnd.range[1]; - - if (after && nextDelta === 0) { - context.report({ - node: punctuatorTokenEnd, - messageId: 'expectedSpaceAfter', - data: { - type, - }, - fix(fixer) { - return fixer.insertTextAfter(punctuatorTokenEnd, ' '); - }, - }); - } else if (!after && nextDelta > 0) { - context.report({ - node: punctuatorTokenEnd, - messageId: 'unexpectedSpaceAfter', - data: { - type, - }, - fix(fixer) { - return fixer.removeRange([ - punctuatorTokenEnd.range[1], - nextToken.range[0], - ]); - }, - }); - } - - if (before && previousDelta === 0) { - context.report({ - node: punctuatorTokenStart, - messageId: 'expectedSpaceBefore', - data: { - type, - }, - fix(fixer) { - return fixer.insertTextAfter(previousToken, ' '); - }, - }); - } else if (!before && previousDelta > 0) { - context.report({ - node: punctuatorTokenStart, - messageId: 'unexpectedSpaceBefore', - data: { - type, - }, - fix(fixer) { - return fixer.removeRange([ - previousToken.range[1], - punctuatorTokenStart.range[0], - ]); - }, - }); - } - } - - return { - TSMappedType(node): void { - if (node.typeAnnotation) { - checkTypeAnnotationSpacing(node.typeAnnotation); - } - }, - TSTypeAnnotation(node): void { - checkTypeAnnotationSpacing(node.typeAnnotation); - }, - }; - }, -}); From 2f44044852da8b4c412b95bd478e78ea0f38cc89 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 14:26:54 -0500 Subject: [PATCH 68/86] Clean up docs --- .../docs/rules/require-types-exports.mdx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/require-types-exports.mdx b/packages/eslint-plugin/docs/rules/require-types-exports.mdx index baa939fc2115..649b4712e1fc 100644 --- a/packages/eslint-plugin/docs/rules/require-types-exports.mdx +++ b/packages/eslint-plugin/docs/rules/require-types-exports.mdx @@ -9,12 +9,10 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/require-types-exports** for documentation. -When exporting entities from a module, it is recommended to export also all the -types that are used in their declarations. This is useful for consumers of the -module, as it allows them to use the types in their own code without having to -use utility types like [`Parameters`](https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype) -or [`ReturnType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype) -in order to extract the types from your code. +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 @@ -28,14 +26,18 @@ type Result = number; export function strLength(arg: Arg): Result { return arg.length; } +``` +```ts interface Fruit { name: string; color: string; } export const getFruitName = (fruit: Fruit) => fruit.name; +``` +```ts enum Color { Red = 'red', Green = 'green', @@ -55,14 +57,18 @@ export type Result = number; export function strLength(arg: Arg): Result { return arg.length; } +``` +```ts export interface Fruit { name: string; color: string; } export const getFruitName = (fruit: Fruit) => fruit.name; +``` +```ts export enum Color { Red = 'red', Green = 'green', @@ -77,4 +83,5 @@ export declare function getRandomColor(): Color; ## When Not To Use It -When you don't want to enforce exporting types that are used in exported functions declarations. +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. From 57181fdd08b3af9749dc36f1b265899731722753 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 14:27:16 -0500 Subject: [PATCH 69/86] Test cleanups --- .../tests/rules/require-types-exports.test.ts | 1011 +++++++++-------- 1 file changed, 506 insertions(+), 505 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 5629431c0711..293c5e0d6099 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -6,10 +6,11 @@ import { getFixturesRootDir } from '../RuleTester'; const rootPath = getFixturesRootDir(); const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: rootPath, - project: './tsconfig-with-dom.json', + languageOptions: { + parserOptions: { + project: './tsconfig-with-dom.json', + tsconfigRootDir: rootPath, + }, }, }); @@ -488,13 +489,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 30, - endColumn: 33, data: { name: 'Arg', }, + endColumn: 33, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -507,13 +508,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 30, - endColumn: 33, data: { name: 'Arg', }, + endColumn: 33, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -526,13 +527,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 37, - endColumn: 40, data: { name: 'Arg', }, + endColumn: 40, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -545,13 +546,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 28, - endColumn: 31, data: { name: 'Arg', }, + endColumn: 31, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -564,13 +565,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 30, - endColumn: 33, data: { name: 'Arg', }, + endColumn: 33, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -583,13 +584,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 30, - endColumn: 33, data: { name: 'Arg', }, + endColumn: 33, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -603,22 +604,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Arg1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 39, - endColumn: 43, data: { name: 'Arg2', }, + endColumn: 43, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -632,22 +633,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Arg1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 39, - endColumn: 43, data: { name: 'Arg2', }, + endColumn: 43, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -664,22 +665,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 30, - endColumn: 34, data: { name: 'Arg1', }, + endColumn: 34, + line: 8, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 8, column: 39, - endColumn: 43, data: { name: 'Arg2', }, + endColumn: 43, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -696,22 +697,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 30, - endColumn: 34, data: { name: 'Arg1', }, + endColumn: 34, + line: 8, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 8, column: 39, - endColumn: 43, data: { name: 'Arg2', }, + endColumn: 43, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -725,22 +726,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Arg1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Arg2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -754,22 +755,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Arg1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Arg2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -783,22 +784,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Arg1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Arg2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -812,22 +813,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Arg1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Arg2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -841,22 +842,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 36, - endColumn: 40, data: { name: 'Arg1', }, + endColumn: 40, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 42, - endColumn: 46, data: { name: 'Arg2', }, + endColumn: 46, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -871,31 +872,31 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 6, column: 36, - endColumn: 40, data: { name: 'Arg1', }, + endColumn: 40, + line: 6, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 42, - endColumn: 46, data: { name: 'Arg2', }, + endColumn: 46, + line: 6, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 60, - endColumn: 64, data: { name: 'Arg3', }, + endColumn: 64, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -909,22 +910,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 36, - endColumn: 40, data: { name: 'Arg1', }, + endColumn: 40, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 42, - endColumn: 46, data: { name: 'Arg2', }, + endColumn: 46, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -938,22 +939,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 42, - endColumn: 46, data: { name: 'Arg1', }, + endColumn: 46, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 51, - endColumn: 55, data: { name: 'Arg2', }, + endColumn: 55, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -967,22 +968,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 42, - endColumn: 46, data: { name: 'Arg1', }, + endColumn: 46, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 51, - endColumn: 55, data: { name: 'Arg2', }, + endColumn: 55, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -995,13 +996,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 36, - endColumn: 39, data: { name: 'Arg', }, + endColumn: 39, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1014,13 +1015,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 36, - endColumn: 39, data: { name: 'Arg', }, + endColumn: 39, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1033,13 +1034,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 30, - endColumn: 33, data: { name: 'Arg', }, + endColumn: 33, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1052,13 +1053,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 30, - endColumn: 33, data: { name: 'Arg', }, + endColumn: 33, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1075,13 +1076,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 30, - endColumn: 35, data: { name: 'Fruit', }, + endColumn: 35, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -1098,13 +1099,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 30, - endColumn: 35, data: { name: 'Fruit', }, + endColumn: 35, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -1117,13 +1118,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 37, - endColumn: 40, data: { name: 'Arg', }, + endColumn: 40, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1137,22 +1138,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Arg1', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Arg2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1166,22 +1167,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Arg1', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Arg2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1195,22 +1196,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Arg1', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Arg2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1224,22 +1225,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Arg1', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Arg2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1252,13 +1253,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 38, - endColumn: 41, data: { name: 'Arg', }, + endColumn: 41, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1271,13 +1272,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 42, - endColumn: 45, data: { name: 'Arg', }, + endColumn: 45, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1290,13 +1291,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 30, - endColumn: 33, data: { name: 'Ret', }, + endColumn: 33, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1309,13 +1310,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 30, - endColumn: 33, data: { name: 'Ret', }, + endColumn: 33, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1329,22 +1330,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Ret1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1358,22 +1359,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Ret1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1387,22 +1388,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Ret1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1416,22 +1417,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 30, - endColumn: 34, data: { name: 'Ret1', }, + endColumn: 34, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1445,22 +1446,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 31, - endColumn: 35, data: { name: 'Ret1', }, + endColumn: 35, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1474,22 +1475,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 31, - endColumn: 35, data: { name: 'Ret1', }, + endColumn: 35, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret2', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1503,22 +1504,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 35, - endColumn: 39, data: { name: 'Ret1', }, + endColumn: 39, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Ret2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1532,22 +1533,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 35, - endColumn: 39, data: { name: 'Ret1', }, + endColumn: 39, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Ret2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1560,13 +1561,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 37, - endColumn: 40, data: { name: 'Ret', }, + endColumn: 40, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1580,22 +1581,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret1', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Ret2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1609,22 +1610,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret1', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Ret2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1638,22 +1639,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret1', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Ret2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1667,22 +1668,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 41, data: { name: 'Ret1', }, + endColumn: 41, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 44, - endColumn: 48, data: { name: 'Ret2', }, + endColumn: 48, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1695,13 +1696,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 38, - endColumn: 41, data: { name: 'Ret', }, + endColumn: 41, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1716,13 +1717,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 23, - endColumn: 26, data: { name: 'Arg', }, + endColumn: 26, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1737,13 +1738,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 32, - endColumn: 35, data: { name: 'Arg', }, + endColumn: 35, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1756,13 +1757,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 38, - endColumn: 41, data: { name: 'Arg', }, + endColumn: 41, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1775,13 +1776,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 38, - endColumn: 41, data: { name: 'Arg', }, + endColumn: 41, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -1800,13 +1801,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 31, - endColumn: 32, data: { name: 'R', }, + endColumn: 32, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -1824,31 +1825,31 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 6, column: 45, - endColumn: 49, data: { name: 'Arg2', }, + endColumn: 49, + line: 6, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 7, column: 24, - endColumn: 28, data: { name: 'Arg1', }, + endColumn: 28, + line: 7, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 9, column: 26, - endColumn: 29, data: { name: 'Ret', }, + endColumn: 29, + line: 9, + messageId: 'requireTypeExport', }, ], }, @@ -1864,22 +1865,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 38, - endColumn: 42, data: { name: 'Arg1', }, + endColumn: 42, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 38, - endColumn: 42, data: { name: 'Arg2', }, + endColumn: 42, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -1894,22 +1895,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 31, - endColumn: 35, data: { name: 'Arg1', }, + endColumn: 35, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 20, - endColumn: 24, data: { name: 'Arg2', }, + endColumn: 24, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -1928,13 +1929,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 32, - endColumn: 37, data: { name: 'A', }, + endColumn: 37, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -1953,13 +1954,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 32, - endColumn: 33, data: { name: 'B', }, + endColumn: 33, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -1980,13 +1981,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 10, column: 32, - endColumn: 33, data: { name: 'B', }, + endColumn: 33, + line: 10, + messageId: 'requireTypeExport', }, ], }, @@ -2008,13 +2009,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 11, column: 32, - endColumn: 33, data: { name: 'B', }, + endColumn: 33, + line: 11, + messageId: 'requireTypeExport', }, ], }, @@ -2035,13 +2036,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 10, column: 32, - endColumn: 33, data: { name: 'B', }, + endColumn: 33, + line: 10, + messageId: 'requireTypeExport', }, ], }, @@ -2066,22 +2067,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 10, column: 44, - endColumn: 46, data: { name: 'T1', }, + endColumn: 46, + line: 10, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 10, column: 64, - endColumn: 66, data: { name: 'T2', }, + endColumn: 66, + line: 10, + messageId: 'requireTypeExport', }, ], }, @@ -2108,22 +2109,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 10, column: 37, - endColumn: 39, data: { name: 'T1', }, + endColumn: 39, + line: 10, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 10, column: 57, - endColumn: 59, data: { name: 'T2', }, + endColumn: 59, + line: 10, + messageId: 'requireTypeExport', }, ], }, @@ -2156,31 +2157,31 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 12, column: 18, - endColumn: 20, data: { name: 'T1', }, + endColumn: 20, + line: 12, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 14, column: 20, - endColumn: 22, data: { name: 'T2', }, + endColumn: 22, + line: 14, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 17, column: 13, - endColumn: 15, data: { name: 'T3', }, + endColumn: 15, + line: 17, + messageId: 'requireTypeExport', }, ], }, @@ -2206,22 +2207,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 22, - endColumn: 23, data: { name: 'A', }, + endColumn: 23, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 23, - endColumn: 24, data: { name: 'B', }, + endColumn: 24, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -2249,22 +2250,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 22, - endColumn: 23, data: { name: 'A', }, + endColumn: 23, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 23, - endColumn: 24, data: { name: 'B', }, + endColumn: 24, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -2286,22 +2287,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 22, - endColumn: 23, data: { name: 'A', }, + endColumn: 23, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 23, - endColumn: 24, data: { name: 'B', }, + endColumn: 24, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -2321,13 +2322,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 22, - endColumn: 27, data: { name: 'Fruit', }, + endColumn: 27, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -2347,13 +2348,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 22, - endColumn: 27, data: { name: 'Fruit', }, + endColumn: 27, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -2373,13 +2374,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 22, - endColumn: 27, data: { name: 'Fruit', }, + endColumn: 27, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -2399,13 +2400,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 9, column: 37, - endColumn: 42, data: { name: 'Fruit', }, + endColumn: 42, + line: 9, + messageId: 'requireTypeExport', }, ], }, @@ -2425,13 +2426,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 9, column: 36, - endColumn: 41, data: { name: 'Fruit', }, + endColumn: 41, + line: 9, + messageId: 'requireTypeExport', }, ], }, @@ -2449,40 +2450,40 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 7, column: 27, - endColumn: 28, data: { name: 'A', }, + endColumn: 28, + line: 7, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 7, column: 30, - endColumn: 31, data: { name: 'B', }, + endColumn: 31, + line: 7, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 7, column: 43, - endColumn: 44, data: { name: 'C', }, + endColumn: 44, + line: 7, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 7, column: 51, - endColumn: 52, data: { name: 'D', }, + endColumn: 52, + line: 7, + messageId: 'requireTypeExport', }, ], }, @@ -2498,22 +2499,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 6, column: 23, - endColumn: 24, data: { name: 'A', }, + endColumn: 24, + line: 6, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 27, - endColumn: 28, data: { name: 'B', }, + endColumn: 28, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -2531,22 +2532,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 6, column: 32, - endColumn: 33, data: { name: 'A', }, + endColumn: 33, + line: 6, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 36, - endColumn: 37, data: { name: 'B', }, + endColumn: 37, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -2564,22 +2565,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 28, - endColumn: 29, data: { name: 'A', }, + endColumn: 29, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 32, - endColumn: 33, data: { name: 'B', }, + endColumn: 33, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -2599,22 +2600,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 38, data: { name: 'A', }, + endColumn: 38, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 41, - endColumn: 42, data: { name: 'B', }, + endColumn: 42, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -2631,13 +2632,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 33, - endColumn: 34, data: { name: 'A', }, + endColumn: 34, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -2656,13 +2657,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 42, - endColumn: 43, data: { name: 'A', }, + endColumn: 43, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -2677,13 +2678,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 28, - endColumn: 29, data: { name: 'A', }, + endColumn: 29, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -2700,13 +2701,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 37, - endColumn: 38, data: { name: 'A', }, + endColumn: 38, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -2723,13 +2724,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 41, - endColumn: 42, data: { name: 'A', }, + endColumn: 42, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -2750,13 +2751,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 41, - endColumn: 46, data: { name: 'Fruit', }, + endColumn: 46, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -2777,13 +2778,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 26, - endColumn: 37, data: { name: 'Fruit', }, + endColumn: 37, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -2804,13 +2805,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 26, - endColumn: 37, data: { name: 'Fruit', }, + endColumn: 37, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -2833,13 +2834,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 8, column: 26, - endColumn: 37, data: { name: 'Fruit', }, + endColumn: 37, + line: 8, + messageId: 'requireTypeExport', }, ], }, @@ -2863,22 +2864,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 9, column: 21, - endColumn: 25, data: { name: 'Item', }, + endColumn: 25, + line: 9, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 11, column: 29, - endColumn: 36, data: { name: 'ItemKey', }, + endColumn: 36, + line: 11, + messageId: 'requireTypeExport', }, ], }, @@ -2895,13 +2896,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 21, - endColumn: 22, data: { name: 'A', }, + endColumn: 22, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -2918,13 +2919,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 7, column: 21, - endColumn: 22, data: { name: 'A', }, + endColumn: 22, + line: 7, + messageId: 'requireTypeExport', }, ], }, @@ -2941,13 +2942,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 7, column: 28, - endColumn: 29, data: { name: 'A', }, + endColumn: 29, + line: 7, + messageId: 'requireTypeExport', }, ], }, @@ -2964,13 +2965,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 7, column: 21, - endColumn: 22, data: { name: 'A', }, + endColumn: 22, + line: 7, + messageId: 'requireTypeExport', }, ], }, @@ -2987,13 +2988,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 7, column: 21, - endColumn: 22, data: { name: 'A', }, + endColumn: 22, + line: 7, + messageId: 'requireTypeExport', }, ], }, @@ -3010,13 +3011,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 41, - endColumn: 42, data: { name: 'A', }, + endColumn: 42, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -3036,13 +3037,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 41, - endColumn: 42, data: { name: 'A', }, + endColumn: 42, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -3060,22 +3061,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 41, - endColumn: 42, data: { name: 'A', }, + endColumn: 42, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 9, column: 37, - endColumn: 38, data: { name: 'B', }, + endColumn: 38, + line: 9, + messageId: 'requireTypeExport', }, ], }, @@ -3099,31 +3100,31 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 6, column: 41, - endColumn: 42, data: { name: 'A', }, + endColumn: 42, + line: 6, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 14, column: 37, - endColumn: 38, data: { name: 'B', }, + endColumn: 38, + line: 14, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 15, column: 37, - endColumn: 38, data: { name: 'C', }, + endColumn: 38, + line: 15, + messageId: 'requireTypeExport', }, ], }, @@ -3145,13 +3146,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 41, - endColumn: 42, data: { name: 'A', }, + endColumn: 42, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -3167,22 +3168,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 41, - endColumn: 42, data: { name: 'A', }, + endColumn: 42, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 25, - endColumn: 26, data: { name: 'B', }, + endColumn: 26, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -3202,13 +3203,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 41, - endColumn: 42, data: { name: 'A', }, + endColumn: 42, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -3224,22 +3225,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 39, - endColumn: 42, data: { name: 'Key', }, + endColumn: 42, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 53, - endColumn: 61, data: { name: 'ItemsMap', }, + endColumn: 61, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -3256,13 +3257,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 22, - endColumn: 23, data: { name: 'A', }, + endColumn: 23, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -3281,22 +3282,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 23, - endColumn: 24, data: { name: 'A', }, + endColumn: 24, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 6, column: 23, - endColumn: 24, data: { name: 'B', }, + endColumn: 24, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -3311,13 +3312,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 6, column: 29, - endColumn: 30, data: { name: 'A', }, + endColumn: 30, + line: 6, + messageId: 'requireTypeExport', }, ], }, @@ -3331,22 +3332,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 31, - endColumn: 36, data: { name: 'Apple', }, + endColumn: 36, + line: 5, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 5, column: 39, - endColumn: 45, data: { name: 'Banana', }, + endColumn: 45, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -3361,13 +3362,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 14, - endColumn: 15, data: { name: 'A', }, + endColumn: 15, + line: 5, + messageId: 'requireTypeExport', }, ], }, @@ -3387,22 +3388,22 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 9, column: 27, - endColumn: 28, data: { name: 'A', }, + endColumn: 28, + line: 9, + messageId: 'requireTypeExport', }, { - messageId: 'requireTypeExport', - line: 10, column: 27, - endColumn: 28, data: { name: 'B', }, + endColumn: 28, + line: 10, + messageId: 'requireTypeExport', }, ], }, @@ -3414,13 +3415,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 3, column: 33, - endColumn: 34, data: { name: 'A', }, + endColumn: 34, + line: 3, + messageId: 'requireTypeExport', }, ], }, @@ -3437,13 +3438,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 52, - endColumn: 65, data: { name: 'typeof fruits', }, + endColumn: 65, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -3458,13 +3459,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 4, column: 56, - endColumn: 75, data: { name: 'typeof fruits.apple', }, + endColumn: 75, + line: 4, + messageId: 'requireTypeExport', }, ], }, @@ -3479,13 +3480,13 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - messageId: 'requireTypeExport', - line: 5, column: 34, - endColumn: 47, data: { name: 'typeof fruits', }, + endColumn: 47, + line: 5, + messageId: 'requireTypeExport', }, ], }, From 8563e09e79b27c16730e88b15b601209e6ad8803 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 14:31:43 -0500 Subject: [PATCH 70/86] Lint and tidy up source a bit --- .../src/rules/require-types-exports.ts | 114 ++++++++---------- 1 file changed, 52 insertions(+), 62 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 222bdeb1edc7..21be28045b30 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,8 +1,9 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; + import { ImplicitLibVariable, ScopeType, } from '@typescript-eslint/scope-manager'; -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, findVariable } from '../util'; @@ -30,29 +31,14 @@ export default createRule<[], MessageIds>({ function collectImportedTypes( node: - | TSESTree.ImportSpecifier + | TSESTree.ImportDefaultSpecifier | TSESTree.ImportNamespaceSpecifier - | TSESTree.ImportDefaultSpecifier, + | TSESTree.ImportSpecifier, ): void { externalizedTypes.add(node.local.name); } function collectExportedTypes(node: TSESTree.Program): void { - const isCollectableType = ( - node: TSESTree.Node, - ): node is - | TSESTree.TSTypeAliasDeclaration - | TSESTree.TSInterfaceDeclaration - | TSESTree.TSEnumDeclaration - | TSESTree.TSModuleDeclaration => { - return [ - AST_NODE_TYPES.TSTypeAliasDeclaration, - AST_NODE_TYPES.TSInterfaceDeclaration, - AST_NODE_TYPES.TSEnumDeclaration, - AST_NODE_TYPES.TSModuleDeclaration, - ].includes(node.type); - }; - node.body.forEach(statement => { if ( statement.type === AST_NODE_TYPES.ExportNamedDeclaration && @@ -67,9 +53,9 @@ export default createRule<[], MessageIds>({ function visitExportedFunctionDeclaration( node: ( - | TSESTree.ExportNamedDeclaration - | TSESTree.DefaultExportDeclarations | TSESTree.ArrowFunctionExpression + | TSESTree.DefaultExportDeclarations + | TSESTree.ExportNamedDeclaration ) & { declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; }, @@ -90,8 +76,8 @@ export default createRule<[], MessageIds>({ function visitExportedTypeDeclaration( node: TSESTree.ExportNamedDeclaration & { declaration: - | TSESTree.TSTypeAliasDeclaration - | TSESTree.TSInterfaceDeclaration; + | TSESTree.TSInterfaceDeclaration + | TSESTree.TSTypeAliasDeclaration; }, ): void { checkNodeTypes(node.declaration); @@ -104,7 +90,7 @@ export default createRule<[], MessageIds>({ } function checkNodeTypes(node: TSESTree.Node): void { - const { typeReferences, typeQueries } = getVisibleTypesRecursively( + const { typeQueries, typeReferences } = getVisibleTypesRecursively( node, context.sourceCode, ); @@ -116,10 +102,7 @@ export default createRule<[], MessageIds>({ function checkTypeReference(node: TSESTree.TSTypeReference): void { const name = getTypeName(node.typeName); - const isExternalized = externalizedTypes.has(name); - const isReported = reportedTypes.has(name); - - if (isExternalized || isReported) { + if (externalizedTypes.has(name) || reportedTypes.has(name)) { return; } @@ -141,6 +124,7 @@ export default createRule<[], MessageIds>({ if (isReported) { return; } + context.report({ node, messageId: 'requireTypeExport', @@ -153,40 +137,29 @@ export default createRule<[], MessageIds>({ } return { - 'ImportDeclaration ImportSpecifier': collectImportedTypes, - 'ImportDeclaration ImportNamespaceSpecifier': collectImportedTypes, - 'ImportDeclaration ImportDefaultSpecifier': collectImportedTypes, - - Program: collectExportedTypes, - - 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': - visitExportedFunctionDeclaration, - - 'ExportNamedDeclaration[declaration.type="TSDeclareFunction"]': + ExportDefaultDeclaration: visitExportDefaultDeclaration, + 'ExportDefaultDeclaration[declaration.type="ArrowFunctionExpression"]': visitExportedFunctionDeclaration, - 'ExportDefaultDeclaration[declaration.type="FunctionDeclaration"]': visitExportedFunctionDeclaration, - - 'ExportDefaultDeclaration[declaration.type="ArrowFunctionExpression"]': + 'ExportNamedDeclaration[declaration.type="FunctionDeclaration"]': + visitExportedFunctionDeclaration, + 'ExportNamedDeclaration[declaration.type="TSDeclareFunction"]': visitExportedFunctionDeclaration, - - 'ExportNamedDeclaration[declaration.type="VariableDeclaration"]': - visitExportedVariableDeclaration, - - 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"]': - visitExportedTypeDeclaration, - - 'ExportNamedDeclaration[declaration.type="TSTypeAliasDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': - visitExportedTypeDeclaration, - 'ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': visitExportedTypeDeclaration, - 'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"] > ExportNamedDeclaration[declaration.type="TSInterfaceDeclaration"]': visitExportedTypeDeclaration, - - ExportDefaultDeclaration: visitExportDefaultDeclaration, + '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, }; }, }); @@ -407,24 +380,41 @@ function getVisibleTypesRecursively( } return { - typeReferences, typeQueries, + typeReferences, }; } -function isInsideFunctionDeclaration(node: TSESTree.Node): boolean { - const functionNodes = new Set([ - AST_NODE_TYPES.ArrowFunctionExpression, - AST_NODE_TYPES.FunctionDeclaration, - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.TSDeclareFunction, - ]); +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 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 (functionNodes.has(node.parent.type)) { + if (functionNodeTypes.has(node.parent.type)) { return true; } From e056cacacc3a4b02075fe6b165eb33e411fa9b6e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 14:35:15 -0500 Subject: [PATCH 71/86] Auto updates --- .../require-types-exports.shot | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) 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 index f515f012d362..6a371cf7dd74 100644 --- 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 @@ -7,10 +7,15 @@ type Arg = string; type Result = number; export function strLength(arg: Arg): Result { - ~~~ Expected type "Arg" to be exported - ~~~~~~ Expected type "Result" to be exported + ~~~ "Arg" is used in other exports from this file, so it should also be exported. + ~~~~~~ "Result" is used in other exports from this file, so it should also be exported. return arg.length; } +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 2`] = ` +"Incorrect interface Fruit { name: string; @@ -18,7 +23,12 @@ interface Fruit { } export const getFruitName = (fruit: Fruit) => fruit.name; - ~~~~~ Expected type "Fruit" to be exported + ~~~~~ "Fruit" is used in other exports from this file, so it should also be exported. +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 3`] = ` +"Incorrect enum Color { Red = 'red', @@ -27,11 +37,11 @@ enum Color { } export declare function getRandomColor(): Color; - ~~~~~ Expected type "Color" to be exported + ~~~~~ "Color" is used in other exports from this file, so it should also be exported. " `; -exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 2`] = ` +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 4`] = ` "Correct export type Arg = string; @@ -40,6 +50,11 @@ export type Result = number; export function strLength(arg: Arg): Result { return arg.length; } +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 5`] = ` +"Correct export interface Fruit { name: string; @@ -47,6 +62,11 @@ export interface Fruit { } export const getFruitName = (fruit: Fruit) => fruit.name; +" +`; + +exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 6`] = ` +"Correct export enum Color { Red = 'red', From 196d1488a0f2c9bb55adcf0749451781d0605996 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 14:42:00 -0500 Subject: [PATCH 72/86] Trim down test empty lines --- .../tests/rules/require-types-exports.test.ts | 167 ------------------ 1 file changed, 167 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 293c5e0d6099..62e9d8a98ea4 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -52,12 +52,10 @@ ruleTester.run('require-types-exports', rule, { return a; } `, - ` type A = number; const f = (a: A): A => a; `, - ` type A = number; type B = string; @@ -65,50 +63,41 @@ ruleTester.run('require-types-exports', rule, { return a; } `, - ` type A = number; type B = string; const f = (a: A | B): any => a; `, - ` type A = number; declare function f(a: A): void; `, - ` type A = number; function f({ a }: { a: A }): A {} `, - ` type A = number; const f = ({ a }: { a: A }): A => {}; `, - ` type A = number; type B = string; function f([a, b]: [A, B]): void {} `, - ` type A = number; type B = string; const f = ([a, b]: [A, B]): void => {}; `, - ` type A = number; function f(a: A): void {} `, - ` type A = number; const f = (a: A): void => {}; `, - ` interface A { a: number; @@ -118,7 +107,6 @@ ruleTester.run('require-types-exports', rule, { return a; } `, - ` interface A { a: number; @@ -126,173 +114,143 @@ ruleTester.run('require-types-exports', rule, { const f = (a: A): A => a; `, - ` export type A = number; export function f(a: A): void {} `, - ` export type A = number; export const f = (a: A): void => {}; `, - ` export type A = number; export type B = string; export function f(a: A | B): void {} `, - ` export type A = number; export type B = string; export const f = (a: A | B): void => {}; `, - ` export type A = number; export type B = string; export function f(a: A & B): void {} `, - ` export type A = number; export type B = string; export const f = (a: A & B): void => {}; `, - ` export type A = number; export function f(...args: A[]): void {} `, - ` export type A = number; export const f = (...args: A[]): void => {}; `, - ` export type A = number; export type B = string; export function f(args: { a: A; b: B; c: number }): void {} `, - ` export type A = number; export type B = string; export const f = (args: { a: A; b: B; c: number }): void => {}; `, - ` export type A = number; export type B = string; export function f(args: [A, B]): void {} `, - ` export type A = number; export type B = string; export const f = (args: [A, B]): void => {}; `, - ` export type A = number; export function f(a: A = 1): void {} `, - ` export type A = number; export const f = (a: A = 1): void => {}; `, - ` export type A = number; export function f(): A {} `, - ` export type A = number; export const f = (): A => {}; `, - ` export type A = number; export type B = string; export function f(): A | B {} `, - ` export type A = number; export type B = string; export const f = (): A | B => {}; `, - ` export type A = number; export type B = string; export function f(): A & B {} `, - ` export type A = number; export type B = string; export const f = (): A & B => {}; `, - ` export type A = number; export type B = string; export function f(): [A, B] {} `, - ` export type A = number; export type B = string; export const f = (): [A, B] => {}; `, - ` export type A = number; export type B = string; export function f(): { a: A; b: B } {} `, - ` export type A = number; export type B = string; export const f = (): { a: A; b: B } => {}; `, - ` import { testFunction, type Arg } from './module'; export function f(a: Arg): void {} `, - ` import { Arg } from './types'; export function f(a: Arg): void {} `, - ` import type { Arg } from './types'; export function f(a: Arg): void {} `, - ` import type { ImportedArg as Arg } from './types'; export function f(a: Arg): void {} `, - ` import type { Arg } from './types'; export function f(a: T): void {} `, - ` export type R = number; @@ -304,7 +262,6 @@ ruleTester.run('require-types-exports', rule, { return value; } `, - ` import type { A } from './types'; @@ -322,7 +279,6 @@ ruleTester.run('require-types-exports', rule, { }, }; `, - ` import type { A } from './types'; @@ -342,7 +298,6 @@ ruleTester.run('require-types-exports', rule, { export default value; `, - ` export enum Fruit { Apple, @@ -352,31 +307,26 @@ ruleTester.run('require-types-exports', rule, { export function f(a: Fruit): void {} `, - ` export function f(arg: Record>) { return arg; } `, - ` export function f>>(arg: T) { return arg; } `, - ` export function f string>>(arg: T) { return arg; } `, - ` export class Wrapper { work(other: this) {} } `, - ` export namespace A { export namespace B { @@ -388,7 +338,6 @@ ruleTester.run('require-types-exports', rule, { return arg; } `, - ` import * as ts from 'typescript'; @@ -396,7 +345,6 @@ ruleTester.run('require-types-exports', rule, { return arg; } `, - ` import ts from 'typescript'; @@ -404,17 +352,14 @@ ruleTester.run('require-types-exports', rule, { return arg; } `, - ` declare const element: HTMLElement; export default element; `, - ` export const date: Date = new Date(); `, - ` import ts from 'typescript'; @@ -445,7 +390,6 @@ ruleTester.run('require-types-exports', rule, { func, }; `, - ` export function func1() { return func2(1); @@ -457,7 +401,6 @@ ruleTester.run('require-types-exports', rule, { return 1; } `, - 'export type ValueOf = T[keyof T];', ` @@ -468,7 +411,6 @@ ruleTester.run('require-types-exports', rule, { return fruits[key]; } `, - ` const fruits = { apple: 'apple' }; @@ -499,7 +441,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -518,7 +459,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -537,7 +477,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -556,7 +495,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -575,7 +513,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -594,7 +531,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -623,7 +559,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -652,7 +587,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -684,7 +618,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -716,7 +649,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -745,7 +677,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -774,7 +705,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -803,7 +733,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -832,7 +761,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -861,7 +789,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -900,7 +827,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -929,7 +855,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -958,7 +883,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -987,7 +911,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1006,7 +929,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1025,7 +947,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1044,7 +965,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1063,7 +983,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` enum Fruit { @@ -1086,7 +1005,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` enum Fruit { @@ -1109,7 +1027,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1128,7 +1045,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -1157,7 +1073,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -1186,7 +1101,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -1215,7 +1129,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -1244,7 +1157,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = string; @@ -1263,7 +1175,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = string; @@ -1282,7 +1193,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret = string; @@ -1301,7 +1211,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret = string; @@ -1320,7 +1229,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1349,7 +1257,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1378,7 +1285,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1407,7 +1313,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1436,7 +1341,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1465,7 +1369,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1494,7 +1397,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1523,7 +1425,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1552,7 +1453,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret = string; @@ -1571,7 +1471,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1600,7 +1499,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1629,7 +1527,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1658,7 +1555,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret1 = string; @@ -1687,7 +1583,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Ret = string; @@ -1706,7 +1601,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1727,7 +1621,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1748,7 +1641,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1767,7 +1659,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg = number; @@ -1786,7 +1677,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type R = number; @@ -1811,7 +1701,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -1853,7 +1742,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -1884,7 +1772,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Arg1 = number; @@ -1914,7 +1801,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` namespace A { @@ -1939,7 +1825,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` namespace A { @@ -1964,7 +1849,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` namespace A { @@ -1991,7 +1875,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` namespace A { @@ -2019,7 +1902,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` namespace A { @@ -2046,7 +1928,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` import type { A } from './types'; @@ -2086,7 +1967,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` import type { A } from './types'; @@ -2128,7 +2008,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type T1 = number; @@ -2185,7 +2064,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = string; @@ -2226,7 +2104,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = string; @@ -2269,7 +2146,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = string; @@ -2306,7 +2182,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Fruit = 'apple' | 'banana'; @@ -2332,7 +2207,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Fruit = 'apple' | 'banana'; @@ -2358,7 +2232,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Fruit = 'apple' | 'banana'; @@ -2384,7 +2257,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Fruit = 'apple' | 'banana'; @@ -2410,7 +2282,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Fruit = 'apple' | 'banana'; @@ -2436,7 +2307,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2487,7 +2357,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2518,7 +2387,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2551,7 +2419,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2584,7 +2451,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2619,7 +2485,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2642,7 +2507,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2667,7 +2531,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2688,7 +2551,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2711,7 +2573,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2734,7 +2595,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` enum Fruit { @@ -2761,7 +2621,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` enum Fruit { @@ -2788,7 +2647,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` enum Fruit { @@ -2815,7 +2673,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` enum Fruit { @@ -2844,7 +2701,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Item = { @@ -2883,7 +2739,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2906,7 +2761,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2929,7 +2783,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2952,7 +2805,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2975,7 +2827,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -2998,7 +2849,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3021,7 +2871,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3047,7 +2896,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3080,7 +2928,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3128,7 +2975,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3156,7 +3002,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3187,7 +3032,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3213,7 +3057,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type ItemsMap = Record; @@ -3244,7 +3087,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3267,7 +3109,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3301,7 +3142,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` declare function func(): string; @@ -3322,7 +3162,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type Apple = 'apple'; @@ -3351,7 +3190,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3372,7 +3210,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = number; @@ -3407,7 +3244,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` type A = 'test'; @@ -3425,7 +3261,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` const fruits = { apple: 'apple' }; @@ -3448,7 +3283,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` const fruits = { apple: 'apple' }; @@ -3469,7 +3303,6 @@ ruleTester.run('require-types-exports', rule, { }, ], }, - { code: ` const fruits = { apple: 'apple' }; From c42d42f194fe8f3128b11530c4a30ceae20cd973 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 15:06:44 -0500 Subject: [PATCH 73/86] More exports --- packages/eslint-plugin/src/rules/array-type.ts | 4 ++-- packages/eslint-plugin/src/rules/ban-ts-comment.ts | 2 +- .../src/rules/consistent-indexed-object-style.ts | 4 ++-- packages/eslint-plugin/src/rules/consistent-return.ts | 4 ++-- packages/eslint-plugin/src/rules/consistent-type-imports.ts | 4 ++-- packages/eslint-plugin/src/rules/naming-convention.ts | 6 ++---- packages/eslint-plugin/src/rules/no-base-to-string.ts | 4 ++-- packages/eslint-plugin/src/rules/no-invalid-void-type.ts | 2 +- packages/eslint-plugin/src/rules/no-use-before-define.ts | 4 ++-- packages/eslint-plugin/src/rules/parameter-properties.ts | 4 ++-- packages/eslint-plugin/src/rules/prefer-destructuring.ts | 4 ++-- .../src/rules/restrict-template-expressions.ts | 2 +- .../eslint-plugin/src/rules/switch-exhaustiveness-check.ts | 4 ++-- packages/eslint-plugin/src/rules/typedef.ts | 4 ++-- .../src/rules/use-unknown-in-catch-callback-variable.ts | 2 +- 15 files changed, 26 insertions(+), 28 deletions(-) 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/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index 61ded7aabf4b..c84334e52bba 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -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/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..52065b1b4bd0 100644 --- a/packages/eslint-plugin/src/rules/consistent-return.ts +++ b/packages/eslint-plugin/src/rules/consistent-return.ts @@ -13,8 +13,8 @@ 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 = | TSESTree.ArrowFunctionExpression diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index 40b088d29245..f7889528500d 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -19,7 +19,7 @@ import { type Prefer = 'no-type-imports' | 'type-imports'; 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/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-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-invalid-void-type.ts b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts index ce41636f9ff3..145f5fdc99ac 100644 --- a/packages/eslint-plugin/src/rules/no-invalid-void-type.ts +++ b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts @@ -9,7 +9,7 @@ interface Options { allowInGenericTypeArguments?: boolean | [string, ...string[]]; } -type MessageIds = +export type MessageIds = | 'invalidVoidForGeneric' | 'invalidVoidNotReturn' | 'invalidVoidNotReturnOrGeneric' 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..b098930dc6c9 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -212,8 +212,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/parameter-properties.ts b/packages/eslint-plugin/src/rules/parameter-properties.ts index 227f30abc014..fd272b70b1d5 100644 --- a/packages/eslint-plugin/src/rules/parameter-properties.ts +++ b/packages/eslint-plugin/src/rules/parameter-properties.ts @@ -15,14 +15,14 @@ type Modifier = 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..5934573e3350 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -19,9 +19,9 @@ type BaseOptions = InferOptionsTypeFromRule; 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/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 3049c1fe3fcf..b6cf99e6de94 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -56,7 +56,7 @@ const optionTesters = ( option: `allow${type}` as const, tester, })); -type Options = [ +export type Options = [ { allow?: TypeOrValueSpecifier[]; } & Partial>, 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/typedef.ts b/packages/eslint-plugin/src/rules/typedef.ts index dd70a97706ec..93c4b718cfcb 100644 --- a/packages/eslint-plugin/src/rules/typedef.ts +++ b/packages/eslint-plugin/src/rules/typedef.ts @@ -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/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' From e11e05a8b1b0a7b39d84663fb83a628365f0fc1f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 15:08:05 -0500 Subject: [PATCH 74/86] More exports --- packages/eslint-plugin/src/rules/await-thenable.ts | 2 +- packages/eslint-plugin/src/rules/ban-ts-comment.ts | 4 ++-- packages/eslint-plugin/src/rules/consistent-type-imports.ts | 4 ++-- .../src/rules/no-confusing-non-null-assertion.ts | 2 +- packages/eslint-plugin/src/rules/no-invalid-void-type.ts | 2 +- packages/eslint-plugin/src/rules/no-use-before-define.ts | 2 +- packages/eslint-plugin/src/rules/parameter-properties.ts | 4 ++-- packages/eslint-plugin/src/rules/prefer-destructuring.ts | 4 ++-- .../eslint-plugin/src/rules/restrict-template-expressions.ts | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) 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 c84334e52bba..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; diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index f7889528500d..6910005c47d7 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -16,8 +16,8 @@ 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'; export type Options = [ { 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-invalid-void-type.ts b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts index 145f5fdc99ac..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,7 +4,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -interface Options { +export interface Options { allowAsThisParameter?: boolean; allowInGenericTypeArguments?: boolean | [string, ...string[]]; } 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 b098930dc6c9..e0dcef814cc7 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -203,7 +203,7 @@ function isInInitializer( return false; } -interface Config { +export interface Config { allowNamedExports?: boolean; classes?: boolean; enums?: boolean; diff --git a/packages/eslint-plugin/src/rules/parameter-properties.ts b/packages/eslint-plugin/src/rules/parameter-properties.ts index fd272b70b1d5..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,7 +13,7 @@ type Modifier = | 'public readonly' | 'readonly'; -type Prefer = 'class-property' | 'parameter-property'; +export type Prefer = 'class-property' | 'parameter-property'; export type Options = [ { diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 5934573e3350..ee4f61767dbe 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -15,8 +15,8 @@ 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]; export type Options = [BaseOptions[0], EnforcementOptions]; diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index b6cf99e6de94..06c071520c0e 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -62,7 +62,7 @@ export type Options = [ } & Partial>, ]; -type MessageId = 'invalidType'; +export type MessageId = 'invalidType'; export default createRule({ name: 'restrict-template-expressions', From cc90f7de1a558cfd8fb7c119474ef6ce9d5661e0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 15:14:52 -0500 Subject: [PATCH 75/86] More exports --- .../src/rules/consistent-return.ts | 2 +- .../src/rules/consistent-type-assertions.ts | 2 +- .../src/rules/member-ordering.ts | 26 +++++++++---------- .../src/rules/no-restricted-types.ts | 2 +- .../gatherLogicalOperands.ts | 2 +- packages/eslint-plugin/src/rules/typedef.ts | 2 +- .../src/util/getWrappingFixer.ts | 2 +- .../rules/prefer-optional-chain/base-cases.ts | 4 +-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-return.ts b/packages/eslint-plugin/src/rules/consistent-return.ts index 52065b1b4bd0..427ede7fe6f7 100644 --- a/packages/eslint-plugin/src/rules/consistent-return.ts +++ b/packages/eslint-plugin/src/rules/consistent-return.ts @@ -16,7 +16,7 @@ const baseRule = getESLintCoreRule('consistent-return'); 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/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/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/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/typedef.ts b/packages/eslint-plugin/src/rules/typedef.ts index 93c4b718cfcb..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', 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/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; From 5458408b1c7d41525c5e840ac9da8c0f43ef14a4 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 15:33:49 -0500 Subject: [PATCH 76/86] fix: don't report within call expressions --- .../src/rules/plugin-test-formatting.ts | 4 +-- .../src/rules/require-types-exports.ts | 11 +++----- .../tests/rules/require-types-exports.test.ts | 27 +++++++++++-------- 3 files changed, 21 insertions(+), 21 deletions(-) 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/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 21be28045b30..7e623f5b46ff 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -109,9 +109,7 @@ export default createRule<[], MessageIds>({ context.report({ node, messageId: 'requireTypeExport', - data: { - name, - }, + data: { name }, }); reportedTypes.add(name); @@ -128,9 +126,7 @@ export default createRule<[], MessageIds>({ context.report({ node, messageId: 'requireTypeExport', - data: { - name, - }, + data: { name }, }); reportedTypes.add(name); @@ -244,8 +240,7 @@ function getVisibleTypesRecursively( case AST_NODE_TYPES.NewExpression: case AST_NODE_TYPES.CallExpression: collect(node.callee); - node.arguments.forEach(arg => collect(arg)); - node.typeArguments?.params.forEach(param => collect(param)); + node.typeArguments?.params.forEach(collect); break; case AST_NODE_TYPES.BinaryExpression: diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 62e9d8a98ea4..9b9290807b2f 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -420,6 +420,17 @@ ruleTester.run('require-types-exports', rule, { return 1; } `, + ` +declare function wrap(listeners: unknown): unknown; + +type Abc = 'abc'; + +export default wrap({ + abc(input: Abc) { + // + }, +}); + `, ], invalid: [ @@ -2720,20 +2731,14 @@ ruleTester.run('require-types-exports', rule, { `, errors: [ { - column: 21, - data: { - name: 'Item', - }, - endColumn: 25, - line: 9, + column: 29, + data: { name: 'ItemKey' }, + line: 11, messageId: 'requireTypeExport', }, { - column: 29, - data: { - name: 'ItemKey', - }, - endColumn: 36, + column: 38, + data: { name: 'Item' }, line: 11, messageId: 'requireTypeExport', }, From 0061e98d0b321d51c603fc74b2c98fb46fa5894e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 22:34:00 -0500 Subject: [PATCH 77/86] Some more cleanups --- .../src/rules/require-types-exports.ts | 199 +++++++++++------- .../tests/rules/require-types-exports.test.ts | 6 +- .../FinancialContributors/Sponsor.tsx | 2 +- .../FinancialContributors/Sponsors/index.tsx | 2 +- .../website/src/components/ast/tsUtils.ts | 2 +- .../components/config/ConfigTypeScript.tsx | 2 +- .../src/components/editor/loadSandbox.ts | 4 +- .../website/src/components/linter/bridge.ts | 4 +- .../src/components/linter/createLinter.ts | 4 +- .../src/components/linter/createParser.ts | 4 +- 10 files changed, 145 insertions(+), 84 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 7e623f5b46ff..7de336ee159d 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -34,11 +34,11 @@ export default createRule<[], MessageIds>({ | TSESTree.ImportDefaultSpecifier | TSESTree.ImportNamespaceSpecifier | TSESTree.ImportSpecifier, - ): void { + ) { externalizedTypes.add(node.local.name); } - function collectExportedTypes(node: TSESTree.Program): void { + function collectExportedTypes(node: TSESTree.Program) { node.body.forEach(statement => { if ( statement.type === AST_NODE_TYPES.ExportNamedDeclaration && @@ -59,7 +59,7 @@ export default createRule<[], MessageIds>({ ) & { declaration: TSESTree.FunctionDeclaration | TSESTree.TSDeclareFunction; }, - ): void { + ) { checkNodeTypes(node.declaration); } @@ -67,7 +67,7 @@ export default createRule<[], MessageIds>({ node: TSESTree.ExportNamedDeclaration & { declaration: TSESTree.VariableDeclaration; }, - ): void { + ) { for (const declaration of node.declaration.declarations) { checkNodeTypes(declaration); } @@ -79,17 +79,17 @@ export default createRule<[], MessageIds>({ | TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeAliasDeclaration; }, - ): void { + ) { checkNodeTypes(node.declaration); } function visitExportDefaultDeclaration( node: TSESTree.ExportDefaultDeclaration, - ): void { + ) { checkNodeTypes(node.declaration); } - function checkNodeTypes(node: TSESTree.Node): void { + function checkNodeTypes(node: TSESTree.Node) { const { typeQueries, typeReferences } = getVisibleTypesRecursively( node, context.sourceCode, @@ -99,7 +99,7 @@ export default createRule<[], MessageIds>({ typeQueries.forEach(checkTypeQuery); } - function checkTypeReference(node: TSESTree.TSTypeReference): void { + function checkTypeReference(node: TSESTree.TSTypeReference) { const name = getTypeName(node.typeName); if (externalizedTypes.has(name) || reportedTypes.has(name)) { @@ -115,8 +115,12 @@ export default createRule<[], MessageIds>({ reportedTypes.add(name); } - function checkTypeQuery(node: TSESTree.TSTypeQuery): void { - const name = context.sourceCode.getText(node); + function checkTypeQuery(node: TSESTree.TSTypeQuery) { + if (node.exprName.type === AST_NODE_TYPES.TSImportType) { + return; + } + + const name = `typeof ${getTypeName(node.exprName)}`; const isReported = reportedTypes.has(name); if (isReported) { @@ -160,53 +164,75 @@ export default createRule<[], MessageIds>({ }, }); -function getTypeName(typeName: TSESTree.EntityName): string { - switch (typeName.type) { +function getLeftmostIdentifier( + node: TSESTree.EntityName | TSESTree.TSImportType, +) { + switch (node.type) { case AST_NODE_TYPES.Identifier: - return typeName.name; + return node.name; case AST_NODE_TYPES.TSQualifiedName: - // Namespaced types are not exported directly, so we check the - // leftmost part of the name. - return getTypeName(typeName.left); + return getLeftmostIdentifier(node.left); + + default: + return undefined; + } +} + +function getTypeName( + node: TSESTree.EntityName, // | TSESTree.TSImportType , +): string { + switch (node.type) { + case AST_NODE_TYPES.Identifier: + return node.name; + + // case AST_NODE_TYPES.TSImportType: + // return `import(${getTypeName(node.argument)})`; + + 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, -): { - typeReferences: Set; - typeQueries: Set; -} { +): VisibleTypes { const typeReferences = new Set(); const typeQueries = new Set(); const visited = new Set(); collect(node); - function collect(node: TSESTree.Node | null | undefined): void { - if (!node || visited.has(node)) { + function collect(child: TSESTree.Node | null | undefined) { + if (!child || visited.has(child)) { return; } - visited.add(node); + visited.add(child); - switch (node.type) { + switch (child.type) { case AST_NODE_TYPES.VariableDeclarator: - collect(node.id); - collect(node.init); + collect(child.id); + collect(child.init); break; case AST_NODE_TYPES.Identifier: { - collect(node.typeAnnotation?.typeAnnotation); + collect(child.typeAnnotation?.typeAnnotation); // Resolve the variable to its declaration (in cases where the variable is referenced) - const scope = sourceCode.getScope(node); - const variableNode = findVariable(scope, node.name); + const scope = sourceCode.getScope(child); + const variableNode = findVariable(scope, child.name); variableNode?.defs.forEach(def => { collect(def.name); @@ -216,7 +242,7 @@ function getVisibleTypesRecursively( } case AST_NODE_TYPES.ObjectExpression: - node.properties.forEach(property => { + child.properties.forEach(property => { const nodeToCheck = property.type === AST_NODE_TYPES.Property ? property.value @@ -227,7 +253,7 @@ function getVisibleTypesRecursively( break; case AST_NODE_TYPES.ArrayExpression: - node.elements.forEach(element => { + child.elements.forEach(element => { const nodeToCheck = element?.type === AST_NODE_TYPES.SpreadElement ? element.argument @@ -239,61 +265,63 @@ function getVisibleTypesRecursively( case AST_NODE_TYPES.NewExpression: case AST_NODE_TYPES.CallExpression: - collect(node.callee); - node.typeArguments?.params.forEach(collect); + collect(child.callee); + child.typeArguments?.params.forEach(collect); break; case AST_NODE_TYPES.BinaryExpression: case AST_NODE_TYPES.LogicalExpression: - collect(node.left); - collect(node.right); + collect(child.left); + collect(child.right); break; case AST_NODE_TYPES.ConditionalExpression: - collect(node.consequent); - collect(node.alternate); + 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: - node.typeParameters?.params.forEach(param => collect(param.constraint)); - node.params.forEach(collect); - collect(node.returnType?.typeAnnotation); - - if (node.body) { - collectFunctionReturnStatements(node).forEach(collect); + child.typeParameters?.params.forEach(param => + collect(param.constraint), + ); + child.params.forEach(collect); + collect(child.returnType?.typeAnnotation); + + if (child.body) { + collectFunctionReturnStatements(child).forEach(collect); } break; case AST_NODE_TYPES.AssignmentPattern: - collect(node.left); + collect(child.left); break; case AST_NODE_TYPES.RestElement: - collect(node.argument); - collect(node.typeAnnotation?.typeAnnotation); + collect(child.argument); + collect(child.typeAnnotation?.typeAnnotation); break; case AST_NODE_TYPES.ObjectPattern: - node.properties.forEach(collect); - collect(node.typeAnnotation?.typeAnnotation); + child.properties.forEach(collect); + collect(child.typeAnnotation?.typeAnnotation); break; case AST_NODE_TYPES.ArrayPattern: - node.elements.forEach(collect); - collect(node.typeAnnotation?.typeAnnotation); + child.elements.forEach(collect); + collect(child.typeAnnotation?.typeAnnotation); break; case AST_NODE_TYPES.ReturnStatement: - collect(node.argument); + collect(child.argument); break; case AST_NODE_TYPES.TSTypeReference: { - const scope = sourceCode.getScope(node); - const variable = findVariable(scope, getTypeName(node.typeName)); + const scope = sourceCode.getScope(child); + const variable = findVariable(scope, getTypeName(child.typeName)); const isBuiltinType = variable instanceof ImplicitLibVariable; @@ -305,71 +333,72 @@ function getVisibleTypesRecursively( ); if (!isBuiltinType && !isGenericTypeArg) { - typeReferences.add(node); + typeReferences.add(child); } - node.typeArguments?.params.forEach(collect); + child.typeArguments?.params.forEach(collect); break; } case AST_NODE_TYPES.TSTypeOperator: - collect(node.typeAnnotation); + collect(child.typeAnnotation); break; case AST_NODE_TYPES.TSTypeQuery: - if (isInsideFunctionDeclaration(node)) { - typeQueries.add(node); + if ( + isInsideFunctionDeclaration(child) && + !isReferencedNameInside(child, node, sourceCode) + ) { + typeQueries.add(child); } + break; case AST_NODE_TYPES.TSArrayType: - collect(node.elementType); + collect(child.elementType); break; case AST_NODE_TYPES.TSTupleType: - node.elementTypes.forEach(collect); + child.elementTypes.forEach(collect); break; case AST_NODE_TYPES.TSUnionType: case AST_NODE_TYPES.TSIntersectionType: - node.types.forEach(collect); + child.types.forEach(collect); break; case AST_NODE_TYPES.TSTypeLiteral: - node.members.forEach(collect); + child.members.forEach(collect); break; case AST_NODE_TYPES.TSTemplateLiteralType: - node.types.forEach(collect); + child.types.forEach(collect); break; case AST_NODE_TYPES.TSTypeAliasDeclaration: - collect(node.typeAnnotation); + collect(child.typeAnnotation); break; case AST_NODE_TYPES.TSInterfaceDeclaration: - node.body.body.forEach(collect); + child.body.body.forEach(collect); break; case AST_NODE_TYPES.TSPropertySignature: - collect(node.typeAnnotation?.typeAnnotation); + collect(child.typeAnnotation?.typeAnnotation); break; case AST_NODE_TYPES.TSQualifiedName: - collect(node.parent); + collect(child.parent); break; case AST_NODE_TYPES.TSAsExpression: - collect(node.expression); - collect(node.typeAnnotation); + collect(child.expression); + collect(child.typeAnnotation); break; case AST_NODE_TYPES.TSIndexedAccessType: - collect(node.objectType); - collect(node.indexType); - break; - - default: + collect(child.objectType); + collect(child.indexType); break; } } @@ -416,6 +445,28 @@ function isInsideFunctionDeclaration(node: TSESTree.Node): boolean { return isInsideFunctionDeclaration(node.parent); } +function isReferencedNameInside( + child: TSESTree.TSTypeQuery, + parent: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +) { + const localName = getLeftmostIdentifier(child.exprName); + if (!localName) { + return false; + } + + const scope = sourceCode.getScope(child); + const identifier = scope.set.get(localName)?.identifiers.at(0); + if (!identifier) { + return false; + } + + return ( + identifier.range[0] >= parent.range[0] && + identifier.range[1] <= parent.range[1] + ); +} + function collectFunctionReturnStatements( functionNode: | TSESTree.ArrowFunctionExpression diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 9b9290807b2f..dd77d2d8df5c 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -431,6 +431,10 @@ export default wrap({ }, }); `, + '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') {}", ], invalid: [ @@ -3300,7 +3304,7 @@ export default wrap({ { column: 56, data: { - name: 'typeof fruits.apple', + name: 'typeof fruits', }, endColumn: 75, line: 4, 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 { From b629590cc58e818368799c4c6c6b75535fd2670f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 22:58:10 -0500 Subject: [PATCH 78/86] Better typeofs and externally declared globals --- .../src/declaration/ExportAndImportKind.ts | 2 +- .../src/rules/require-types-exports.ts | 65 ++++++++++++------- .../tests/rules/require-types-exports.test.ts | 5 +- 3 files changed, 48 insertions(+), 24 deletions(-) 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/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 7de336ee159d..46962930bb62 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -8,9 +8,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, findVariable } from '../util'; -export type MessageIds = 'requireTypeExport'; - -export default createRule<[], MessageIds>({ +export default createRule({ name: 'require-types-exports', meta: { type: 'suggestion', @@ -101,11 +99,15 @@ export default createRule<[], MessageIds>({ function checkTypeReference(node: TSESTree.TSTypeReference) { const name = getTypeName(node.typeName); - if (externalizedTypes.has(name) || reportedTypes.has(name)) { return; } + const declaration = findVariable(context.sourceCode.getScope(node), name); + if (!declaration) { + return; + } + context.report({ node, messageId: 'requireTypeExport', @@ -120,15 +122,25 @@ export default createRule<[], MessageIds>({ return; } - const name = `typeof ${getTypeName(node.exprName)}`; - const isReported = reportedTypes.has(name); + const queriedName = getTypeName(node.exprName); + const name = `typeof ${queriedName}`; + const isReported = reportedTypes.has(name); if (isReported) { return; } + const declaration = findVariable( + context.sourceCode.getScope(node), + queriedName, + ); + if (!declaration) { + return; + } + context.report({ node, + // messageId: 'requireTypeQueryExport', messageId: 'requireTypeExport', data: { name }, }); @@ -165,7 +177,7 @@ export default createRule<[], MessageIds>({ }); function getLeftmostIdentifier( - node: TSESTree.EntityName | TSESTree.TSImportType, + node: TSESTree.EntityName | TSESTree.TSImportType | TSESTree.TSTypeReference, ) { switch (node.type) { case AST_NODE_TYPES.Identifier: @@ -179,9 +191,7 @@ function getLeftmostIdentifier( } } -function getTypeName( - node: TSESTree.EntityName, // | TSESTree.TSImportType , -): string { +function getTypeName(node: TSESTree.EntityName): string { switch (node.type) { case AST_NODE_TYPES.Identifier: return node.name; @@ -347,7 +357,7 @@ function getVisibleTypesRecursively( case AST_NODE_TYPES.TSTypeQuery: if ( isInsideFunctionDeclaration(child) && - !isReferencedNameInside(child, node, sourceCode) + !isReferencedNameInside(child.exprName, node, sourceCode) ) { typeQueries.add(child); } @@ -445,26 +455,37 @@ function isInsideFunctionDeclaration(node: TSESTree.Node): boolean { 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 isDeclarationInside( + declaration: TSESTree.Node, + parent: TSESTree.Node, +) { + return ( + declaration.range[0] >= parent.range[0] && + declaration.range[1] <= parent.range[1] + ); +} + function isReferencedNameInside( - child: TSESTree.TSTypeQuery, + child: TSESTree.EntityName | TSESTree.TSImportType, parent: TSESTree.Node, sourceCode: TSESLint.SourceCode, ) { - const localName = getLeftmostIdentifier(child.exprName); + const localName = getLeftmostIdentifier(child); if (!localName) { return false; } - const scope = sourceCode.getScope(child); - const identifier = scope.set.get(localName)?.identifiers.at(0); - if (!identifier) { - return false; - } + const declaration = getDeclarationForName(child, localName, sourceCode); - return ( - identifier.range[0] >= parent.range[0] && - identifier.range[1] <= parent.range[1] - ); + return !!declaration && isDeclarationInside(declaration, parent); } function collectFunctionReturnStatements( diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index dd77d2d8df5c..1f28803bbf02 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -435,8 +435,11 @@ export default wrap({ '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']) {}", ], - invalid: [ { code: ` From 6de73eb3ab12c8f189ac087cd4b7902cd2b2d732 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 23:11:14 -0500 Subject: [PATCH 79/86] shared isNodeInside, and more internal progress --- .../ast-spec/tests/util/parsers/parser-types.ts | 4 ++-- packages/eslint-plugin/src/rules/no-loop-func.ts | 5 ++--- .../src/rules/no-use-before-define.ts | 7 ++----- .../src/rules/require-types-exports.ts | 14 ++------------ packages/eslint-plugin/src/util/astUtils.ts | 7 +++++++ packages/rule-tester/src/types/index.ts | 2 +- packages/rule-tester/src/utils/SourceCodeFixer.ts | 2 +- packages/rule-tester/src/utils/config-validator.ts | 2 +- .../rule-tester/src/utils/flat-config-schema.ts | 6 +++--- packages/typescript-estree/src/node-utils.ts | 13 ++++++------- .../typescript-estree/src/parseSettings/index.ts | 2 +- packages/typescript-estree/src/parser-options.ts | 2 +- packages/typescript-estree/src/simple-traverse.ts | 2 +- .../src/ast-utils/eslint-utils/ReferenceTracker.ts | 2 +- 14 files changed, 31 insertions(+), 39 deletions(-) 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/src/rules/no-loop-func.ts b/packages/eslint-plugin/src/rules/no-loop-func.ts index ac4e6a27875f..7446d767a6d8 100644 --- a/packages/eslint-plugin/src/rules/no-loop-func.ts +++ b/packages/eslint-plugin/src/rules/no-loop-func.ts @@ -7,7 +7,7 @@ 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'); @@ -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-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index e0dcef814cc7..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; } } diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 46962930bb62..6691e8dd93da 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -6,7 +6,7 @@ import { } from '@typescript-eslint/scope-manager'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule, findVariable } from '../util'; +import { createRule, findVariable, isNodeInside } from '../util'; export default createRule({ name: 'require-types-exports', @@ -463,16 +463,6 @@ function getDeclarationForName( return sourceCode.getScope(node).set.get(name)?.identifiers.at(0); } -function isDeclarationInside( - declaration: TSESTree.Node, - parent: TSESTree.Node, -) { - return ( - declaration.range[0] >= parent.range[0] && - declaration.range[1] <= parent.range[1] - ); -} - function isReferencedNameInside( child: TSESTree.EntityName | TSESTree.TSImportType, parent: TSESTree.Node, @@ -485,7 +475,7 @@ function isReferencedNameInside( const declaration = getDeclarationForName(child, localName, sourceCode); - return !!declaration && isDeclarationInside(declaration, parent); + return !!declaration && isNodeInside(declaration, parent); } function collectFunctionReturnStatements( 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/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/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-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/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; From 4188eb02e19512d0d7ffdfb46ba20e23ad66042f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 23:24:50 -0500 Subject: [PATCH 80/86] Handle already-exported ones, such as namespace children --- .../src/rules/require-types-exports.ts | 53 +++++++++++++++++-- .../tests/rules/require-types-exports.test.ts | 15 ++++++ .../src/utils/deprecation-warnings.ts | 2 +- .../tests/test-utils/test-utils.ts | 2 +- packages/utils/src/ts-eslint/Rule.ts | 15 +++--- 5 files changed, 74 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 6691e8dd93da..094a38d1e578 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -1,12 +1,22 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +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 { createRule, findVariable, isNodeInside } from '../util'; +import { + createRule, + findVariable, + getParserServices, + isNodeInside, +} from '../util'; export default createRule({ name: 'require-types-exports', @@ -18,7 +28,7 @@ export default createRule({ }, messages: { requireTypeExport: - '"{{ name }}" is used in other exports from this file, so it should also be exported.', + '"{{ name }}" is used in other exports, so it should also be exported.', }, schema: [], }, @@ -103,8 +113,14 @@ export default createRule({ return; } - const declaration = findVariable(context.sourceCode.getScope(node), name); - if (!declaration) { + const declaration = findVariable( + context.sourceCode.getScope(node), + name, + )?.identifiers.at(0); + if ( + !declaration || + isDeclarationExported(declaration, getParserServices(context, true)) + ) { return; } @@ -436,6 +452,33 @@ function isCollectableType( 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, + services: ParserServices, +) { + if (!declaration.parent) { + return false; + } + + if (exportNodeTypes.has(declaration.parent.type)) { + return true; + } + + if ( + 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, diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 1f28803bbf02..23bd3fcdffad 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -439,6 +439,21 @@ export default wrap({ '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; +} + `, ], invalid: [ { 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/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/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 { From 55cca122c001bf2d24f4fd10da5122e298e7d844 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 23:31:50 -0500 Subject: [PATCH 81/86] Also check typeof reports for being exported already --- .../src/rules/require-types-exports.ts | 13 +++++++------ .../tests/rules/require-types-exports.test.ts | 7 +++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 094a38d1e578..6068a40f6374 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -113,10 +113,8 @@ export default createRule({ return; } - const declaration = findVariable( - context.sourceCode.getScope(node), - name, - )?.identifiers.at(0); + const declaration = findVariable(context.sourceCode.getScope(node), name) + ?.identifiers[0]; if ( !declaration || isDeclarationExported(declaration, getParserServices(context, true)) @@ -149,8 +147,11 @@ export default createRule({ const declaration = findVariable( context.sourceCode.getScope(node), queriedName, - ); - if (!declaration) { + )?.identifiers[0]; + if ( + !declaration || + isDeclarationExported(declaration, getParserServices(context, true)) + ) { return; } diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 23bd3fcdffad..9472edb664a0 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -452,6 +452,13 @@ export namespace Values { 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); } `, ], From dd7c4543bce52c225ac3085bf0b99e494041c71a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 23:47:49 -0500 Subject: [PATCH 82/86] requireTypeQueryExport message --- .../src/rules/require-types-exports.ts | 58 +++++++++---------- .../require-types-exports.shot | 8 +-- .../tests/rules/require-types-exports.test.ts | 6 +- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 6068a40f6374..864193939fb5 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -18,6 +18,8 @@ import { isNodeInside, } from '../util'; +export type MessageId = 'requireTypeExport' | 'requireTypeQueryExport'; + export default createRule({ name: 'require-types-exports', meta: { @@ -29,13 +31,15 @@ export default createRule({ messages: { requireTypeExport: '"{{ name }}" is used in other exports, so it should also be exported.', + requireTypeQueryExport: + '"{{ name }}" is used in other exports, so it should also be exported as its own type.', }, schema: [], }, defaultOptions: [], create(context) { const externalizedTypes = new Set(); - const reportedTypes = new Set(); + const reportedNodes = new Set(); function collectImportedTypes( node: @@ -109,26 +113,11 @@ export default createRule({ function checkTypeReference(node: TSESTree.TSTypeReference) { const name = getTypeName(node.typeName); - if (externalizedTypes.has(name) || reportedTypes.has(name)) { + if (externalizedTypes.has(name)) { return; } - const declaration = findVariable(context.sourceCode.getScope(node), name) - ?.identifiers[0]; - if ( - !declaration || - isDeclarationExported(declaration, getParserServices(context, true)) - ) { - return; - } - - context.report({ - node, - messageId: 'requireTypeExport', - data: { name }, - }); - - reportedTypes.add(name); + reportIfNeeded(name, name, node, 'requireTypeExport'); } function checkTypeQuery(node: TSESTree.TSTypeQuery) { @@ -136,20 +125,30 @@ export default createRule({ return; } - const queriedName = getTypeName(node.exprName); - const name = `typeof ${queriedName}`; + const nameQueried = getTypeName(node.exprName); + const nameType = `typeof ${nameQueried}`; - const isReported = reportedTypes.has(name); - if (isReported) { - return; - } + reportIfNeeded(nameQueried, nameType, node, 'requireTypeQueryExport'); + } + function reportIfNeeded( + nameQueried: string, + nameType: string, + node: TSESTree.Node, + messageId: MessageId, + ) { const declaration = findVariable( context.sourceCode.getScope(node), - queriedName, + nameQueried, )?.identifiers[0]; + + if (!declaration || reportedNodes.has(declaration)) { + return; + } + + reportedNodes.add(declaration); + if ( - !declaration || isDeclarationExported(declaration, getParserServices(context, true)) ) { return; @@ -157,12 +156,9 @@ export default createRule({ context.report({ node, - // messageId: 'requireTypeQueryExport', - messageId: 'requireTypeExport', - data: { name }, + messageId, + data: { name: nameType }, }); - - reportedTypes.add(name); } return { 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 index 6a371cf7dd74..11bcc9ae95d0 100644 --- 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 @@ -7,8 +7,8 @@ type Arg = string; type Result = number; export function strLength(arg: Arg): Result { - ~~~ "Arg" is used in other exports from this file, so it should also be exported. - ~~~~~~ "Result" is used in other exports from this file, so it should also be exported. + ~~~ "Arg" is used in other exports, so it should also be exported. + ~~~~~~ "Result" is used in other exports, so it should also be exported. return arg.length; } " @@ -23,7 +23,7 @@ interface Fruit { } export const getFruitName = (fruit: Fruit) => fruit.name; - ~~~~~ "Fruit" is used in other exports from this file, so it should also be exported. + ~~~~~ "Fruit" is used in other exports, so it should also be exported. " `; @@ -37,7 +37,7 @@ enum Color { } export declare function getRandomColor(): Color; - ~~~~~ "Color" is used in other exports from this file, so it should also be exported. + ~~~~~ "Color" is used in other exports, so it should also be exported. " `; diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 9472edb664a0..28e1f3d1222d 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -3313,7 +3313,7 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { }, endColumn: 65, line: 4, - messageId: 'requireTypeExport', + messageId: 'requireTypeQueryExport', }, ], }, @@ -3333,7 +3333,7 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { }, endColumn: 75, line: 4, - messageId: 'requireTypeExport', + messageId: 'requireTypeQueryExport', }, ], }, @@ -3353,7 +3353,7 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { }, endColumn: 47, line: 5, - messageId: 'requireTypeExport', + messageId: 'requireTypeQueryExport', }, ], }, From 6d04460cb4083ea5377bff273cd95d497fd64f8b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 26 Dec 2024 23:49:12 -0500 Subject: [PATCH 83/86] requireTypeQueryExport message tweak --- packages/eslint-plugin/src/rules/require-types-exports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 864193939fb5..ea3a308d0b3d 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -32,7 +32,7 @@ export default createRule({ requireTypeExport: '"{{ name }}" is used in other exports, so it should also be exported.', requireTypeQueryExport: - '"{{ name }}" is used in other exports, so it should also be exported as its own type.', + '"{{ name }}" is used in other exports, so `{{ name }}` or `typeof {{ name }}` should also be exported.', }, schema: [], }, From e9ada943a8efeb26c2334cb81db308db7ba8a55c Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 27 Dec 2024 00:27:01 -0500 Subject: [PATCH 84/86] Switched report to be on export --- .../docs/rules/require-types-exports.mdx | 36 +- .../src/rules/require-types-exports.ts | 26 +- .../tests/rules/require-types-exports.test.ts | 1014 ++++++++--------- 3 files changed, 534 insertions(+), 542 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/require-types-exports.mdx b/packages/eslint-plugin/docs/rules/require-types-exports.mdx index 649b4712e1fc..a96076bf4ca8 100644 --- a/packages/eslint-plugin/docs/rules/require-types-exports.mdx +++ b/packages/eslint-plugin/docs/rules/require-types-exports.mdx @@ -19,15 +19,6 @@ Otherwise, consumers may have to use utility types like [`Parameters`](https://w -```ts -type Arg = string; -type Result = number; - -export function strLength(arg: Arg): Result { - return arg.length; -} -``` - ```ts interface Fruit { name: string; @@ -37,6 +28,15 @@ interface Fruit { 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', @@ -50,15 +50,6 @@ export declare function getRandomColor(): Color; -```ts -export type Arg = string; -export type Result = number; - -export function strLength(arg: Arg): Result { - return arg.length; -} -``` - ```ts export interface Fruit { name: string; @@ -68,6 +59,15 @@ export interface Fruit { 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', diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index ea3a308d0b3d..78cb3c571b8a 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -30,9 +30,9 @@ export default createRule({ }, messages: { requireTypeExport: - '"{{ name }}" is used in other exports, so it should also be exported.', + '`{{ name }}` is used in exports, so it should also be exported.', requireTypeQueryExport: - '"{{ name }}" is used in other exports, so `{{ name }}` or `typeof {{ name }}` should also be exported.', + '`typeof {{ name }}` is used in exports, so `{{ name }}` and/or a standalone `typeof {{ name }}` should also be exported.', }, schema: [], }, @@ -117,7 +117,7 @@ export default createRule({ return; } - reportIfNeeded(name, name, node, 'requireTypeExport'); + reportIfNeeded(name, node, 'requireTypeExport'); } function checkTypeQuery(node: TSESTree.TSTypeQuery) { @@ -126,21 +126,16 @@ export default createRule({ } const nameQueried = getTypeName(node.exprName); - const nameType = `typeof ${nameQueried}`; - - reportIfNeeded(nameQueried, nameType, node, 'requireTypeQueryExport'); + reportIfNeeded(nameQueried, node, 'requireTypeQueryExport'); } function reportIfNeeded( - nameQueried: string, - nameType: string, + name: string, node: TSESTree.Node, messageId: MessageId, ) { - const declaration = findVariable( - context.sourceCode.getScope(node), - nameQueried, - )?.identifiers[0]; + const declaration = findVariable(context.sourceCode.getScope(node), name) + ?.identifiers[0]; if (!declaration || reportedNodes.has(declaration)) { return; @@ -155,9 +150,9 @@ export default createRule({ } context.report({ - node, + node: declaration, messageId, - data: { name: nameType }, + data: { name }, }); } @@ -209,9 +204,6 @@ function getTypeName(node: TSESTree.EntityName): string { case AST_NODE_TYPES.Identifier: return node.name; - // case AST_NODE_TYPES.TSImportType: - // return `import(${getTypeName(node.argument)})`; - case AST_NODE_TYPES.TSQualifiedName: // Namespaced types such as enums are not exported directly, // so we check the leftmost part of the name. diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index 28e1f3d1222d..fce30ea53776 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -471,12 +471,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg', }, - endColumn: 33, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -489,12 +489,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg', }, - endColumn: 33, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -507,12 +507,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Arg', }, - endColumn: 40, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -525,12 +525,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 28, + column: 14, data: { name: 'Arg', }, - endColumn: 31, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -543,12 +543,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg', }, - endColumn: 33, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -561,12 +561,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg', }, - endColumn: 33, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -580,21 +580,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 39, + column: 14, data: { name: 'Arg2', }, - endColumn: 43, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -608,21 +608,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 39, + column: 14, data: { name: 'Arg2', }, - endColumn: 43, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -639,21 +639,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg1', }, - endColumn: 34, - line: 8, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 39, + column: 19, data: { name: 'Arg2', }, - endColumn: 43, - line: 8, + endColumn: 23, + line: 4, messageId: 'requireTypeExport', }, ], @@ -670,21 +670,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg1', }, - endColumn: 34, - line: 8, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 39, + column: 19, data: { name: 'Arg2', }, - endColumn: 43, - line: 8, + endColumn: 23, + line: 4, messageId: 'requireTypeExport', }, ], @@ -698,21 +698,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Arg2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -726,21 +726,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Arg2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -754,21 +754,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Arg2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -782,21 +782,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Arg2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -810,21 +810,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 36, + column: 14, data: { name: 'Arg1', }, - endColumn: 40, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 42, + column: 14, data: { name: 'Arg2', }, - endColumn: 46, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -839,30 +839,30 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 36, + column: 14, data: { name: 'Arg1', }, - endColumn: 40, - line: 6, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 42, + column: 14, data: { name: 'Arg2', }, - endColumn: 46, - line: 6, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, { - column: 60, + column: 14, data: { name: 'Arg3', }, - endColumn: 64, - line: 6, + endColumn: 18, + line: 4, messageId: 'requireTypeExport', }, ], @@ -876,21 +876,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 36, + column: 14, data: { name: 'Arg1', }, - endColumn: 40, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 42, + column: 14, data: { name: 'Arg2', }, - endColumn: 46, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -904,21 +904,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 42, + column: 14, data: { name: 'Arg1', }, - endColumn: 46, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 51, + column: 14, data: { name: 'Arg2', }, - endColumn: 55, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -932,21 +932,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 42, + column: 14, data: { name: 'Arg1', }, - endColumn: 46, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 51, + column: 14, data: { name: 'Arg2', }, - endColumn: 55, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -959,12 +959,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 36, + column: 14, data: { name: 'Arg', }, - endColumn: 39, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -977,12 +977,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 36, + column: 14, data: { name: 'Arg', }, - endColumn: 39, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -995,12 +995,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg', }, - endColumn: 33, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1013,12 +1013,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Arg', }, - endColumn: 33, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1035,12 +1035,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Fruit', }, - endColumn: 35, - line: 8, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1057,12 +1057,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Fruit', }, - endColumn: 35, - line: 8, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1075,12 +1075,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Arg', }, - endColumn: 40, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1094,21 +1094,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Arg1', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Arg2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1122,21 +1122,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Arg1', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Arg2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1150,21 +1150,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Arg1', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Arg2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1178,21 +1178,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Arg1', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Arg2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1205,12 +1205,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 38, + column: 14, data: { name: 'Arg', }, - endColumn: 41, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1223,12 +1223,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 42, + column: 14, data: { name: 'Arg', }, - endColumn: 45, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1241,12 +1241,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Ret', }, - endColumn: 33, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1259,12 +1259,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Ret', }, - endColumn: 33, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1278,21 +1278,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Ret1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Ret2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1306,21 +1306,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Ret1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Ret2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1334,21 +1334,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Ret1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Ret2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1362,21 +1362,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 30, + column: 14, data: { name: 'Ret1', }, - endColumn: 34, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Ret2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1390,21 +1390,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 31, + column: 14, data: { name: 'Ret1', }, - endColumn: 35, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Ret2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1418,21 +1418,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 31, + column: 14, data: { name: 'Ret1', }, - endColumn: 35, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'Ret2', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1446,21 +1446,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 35, + column: 14, data: { name: 'Ret1', }, - endColumn: 39, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Ret2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1474,21 +1474,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 35, + column: 14, data: { name: 'Ret1', }, - endColumn: 39, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Ret2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1501,12 +1501,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Ret', }, - endColumn: 40, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1520,21 +1520,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Ret1', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Ret2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1548,21 +1548,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Ret1', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Ret2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1576,21 +1576,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Ret1', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Ret2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1604,21 +1604,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Ret1', }, - endColumn: 41, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 44, + column: 14, data: { name: 'Ret2', }, - endColumn: 48, - line: 5, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1631,12 +1631,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 38, + column: 14, data: { name: 'Ret', }, - endColumn: 41, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1651,12 +1651,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 23, + column: 14, data: { name: 'Arg', }, - endColumn: 26, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1671,12 +1671,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 32, + column: 14, data: { name: 'Arg', }, - endColumn: 35, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1689,12 +1689,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 38, + column: 14, data: { name: 'Arg', }, - endColumn: 41, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1707,12 +1707,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 38, + column: 14, data: { name: 'Arg', }, - endColumn: 41, - line: 4, + endColumn: 17, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1731,12 +1731,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 31, + column: 14, data: { name: 'R', }, - endColumn: 32, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1754,30 +1754,30 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 45, + column: 14, data: { - name: 'Arg2', + name: 'Arg1', }, - endColumn: 49, - line: 6, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 24, + column: 14, data: { - name: 'Arg1', + name: 'Arg2', }, - endColumn: 28, - line: 7, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, { - column: 26, + column: 14, data: { name: 'Ret', }, - endColumn: 29, - line: 9, + endColumn: 17, + line: 4, messageId: 'requireTypeExport', }, ], @@ -1793,21 +1793,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 38, + column: 14, data: { name: 'Arg1', }, - endColumn: 42, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 38, + column: 14, data: { name: 'Arg2', }, - endColumn: 42, - line: 6, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1822,21 +1822,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 31, + column: 14, data: { name: 'Arg1', }, - endColumn: 35, - line: 5, + endColumn: 18, + line: 2, messageId: 'requireTypeExport', }, { - column: 20, + column: 14, data: { name: 'Arg2', }, - endColumn: 24, - line: 6, + endColumn: 18, + line: 3, messageId: 'requireTypeExport', }, ], @@ -1855,12 +1855,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 32, + column: 19, data: { name: 'A', }, - endColumn: 37, - line: 8, + endColumn: 20, + line: 2, messageId: 'requireTypeExport', }, ], @@ -1879,12 +1879,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 32, + column: 14, data: { name: 'B', }, - endColumn: 33, - line: 8, + endColumn: 15, + line: 6, messageId: 'requireTypeExport', }, ], @@ -1905,12 +1905,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 32, + column: 14, data: { name: 'B', }, - endColumn: 33, - line: 10, + endColumn: 15, + line: 8, messageId: 'requireTypeExport', }, ], @@ -1932,12 +1932,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 32, + column: 14, data: { name: 'B', }, - endColumn: 33, - line: 11, + endColumn: 15, + line: 9, messageId: 'requireTypeExport', }, ], @@ -1958,12 +1958,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 32, + column: 14, data: { name: 'B', }, - endColumn: 33, - line: 10, + endColumn: 15, + line: 8, messageId: 'requireTypeExport', }, ], @@ -1988,21 +1988,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 44, + column: 14, data: { name: 'T1', }, - endColumn: 46, - line: 10, + endColumn: 16, + line: 4, messageId: 'requireTypeExport', }, { - column: 64, + column: 19, data: { name: 'T2', }, - endColumn: 66, - line: 10, + endColumn: 21, + line: 6, messageId: 'requireTypeExport', }, ], @@ -2029,21 +2029,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'T1', }, - endColumn: 39, - line: 10, + endColumn: 16, + line: 4, messageId: 'requireTypeExport', }, { - column: 57, + column: 19, data: { name: 'T2', }, - endColumn: 59, - line: 10, + endColumn: 21, + line: 6, messageId: 'requireTypeExport', }, ], @@ -2076,30 +2076,30 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 18, + column: 14, data: { name: 'T1', }, - endColumn: 20, - line: 12, + endColumn: 16, + line: 2, messageId: 'requireTypeExport', }, { - column: 20, + column: 19, data: { name: 'T2', }, - endColumn: 22, - line: 14, + endColumn: 21, + line: 4, messageId: 'requireTypeExport', }, { - column: 13, + column: 14, data: { name: 'T3', }, - endColumn: 15, - line: 17, + endColumn: 16, + line: 8, messageId: 'requireTypeExport', }, ], @@ -2125,21 +2125,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 22, + column: 14, data: { name: 'A', }, - endColumn: 23, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 23, + column: 14, data: { name: 'B', }, - endColumn: 24, - line: 6, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -2167,21 +2167,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 22, + column: 14, data: { name: 'A', }, - endColumn: 23, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 23, + column: 14, data: { name: 'B', }, - endColumn: 24, - line: 6, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -2203,21 +2203,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 22, + column: 14, data: { name: 'A', }, - endColumn: 23, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 23, + column: 14, data: { name: 'B', }, - endColumn: 24, - line: 6, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -2237,12 +2237,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 22, + column: 14, data: { name: 'Fruit', }, - endColumn: 27, - line: 4, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2262,12 +2262,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 22, + column: 14, data: { name: 'Fruit', }, - endColumn: 27, - line: 4, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2287,12 +2287,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 22, + column: 14, data: { name: 'Fruit', }, - endColumn: 27, - line: 4, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2312,12 +2312,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'Fruit', }, - endColumn: 42, - line: 9, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2337,12 +2337,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 36, + column: 14, data: { name: 'Fruit', }, - endColumn: 41, - line: 9, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2360,39 +2360,39 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 27, + column: 14, data: { name: 'A', }, - endColumn: 28, - line: 7, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 30, + column: 14, data: { name: 'B', }, - endColumn: 31, - line: 7, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, { - column: 43, + column: 14, data: { name: 'C', }, - endColumn: 44, - line: 7, + endColumn: 15, + line: 4, messageId: 'requireTypeExport', }, { - column: 51, + column: 14, data: { name: 'D', }, - endColumn: 52, - line: 7, + endColumn: 15, + line: 5, messageId: 'requireTypeExport', }, ], @@ -2408,21 +2408,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 23, + column: 14, data: { name: 'A', }, - endColumn: 24, - line: 6, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 27, + column: 14, data: { name: 'B', }, - endColumn: 28, - line: 6, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -2440,21 +2440,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 32, + column: 14, data: { name: 'A', }, - endColumn: 33, - line: 6, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 36, + column: 14, data: { name: 'B', }, - endColumn: 37, - line: 6, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -2472,21 +2472,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 28, + column: 14, data: { name: 'A', }, - endColumn: 29, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 32, + column: 14, data: { name: 'B', }, - endColumn: 33, - line: 5, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -2506,21 +2506,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'A', }, - endColumn: 38, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 41, + column: 14, data: { name: 'B', }, - endColumn: 42, - line: 5, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -2537,12 +2537,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 33, + column: 14, data: { name: 'A', }, - endColumn: 34, - line: 4, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2561,12 +2561,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 42, + column: 14, data: { name: 'A', }, - endColumn: 43, - line: 4, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2581,12 +2581,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 28, + column: 14, data: { name: 'A', }, - endColumn: 29, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2603,12 +2603,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 37, + column: 14, data: { name: 'A', }, - endColumn: 38, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2625,12 +2625,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'A', }, - endColumn: 42, - line: 4, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2651,12 +2651,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'Fruit', }, - endColumn: 46, - line: 8, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2677,12 +2677,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 26, + column: 14, data: { name: 'Fruit', }, - endColumn: 37, - line: 8, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2703,12 +2703,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 26, + column: 14, data: { name: 'Fruit', }, - endColumn: 37, - line: 8, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2731,12 +2731,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 26, + column: 14, data: { name: 'Fruit', }, - endColumn: 37, - line: 8, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2760,15 +2760,15 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 29, - data: { name: 'ItemKey' }, - line: 11, + column: 14, + data: { name: 'Item' }, + line: 2, messageId: 'requireTypeExport', }, { - column: 38, - data: { name: 'Item' }, - line: 11, + column: 14, + data: { name: 'ItemKey' }, + line: 7, messageId: 'requireTypeExport', }, ], @@ -2785,12 +2785,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 21, + column: 14, data: { name: 'A', }, - endColumn: 22, - line: 4, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2807,12 +2807,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 21, + column: 14, data: { name: 'A', }, - endColumn: 22, - line: 7, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2829,12 +2829,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 28, + column: 14, data: { name: 'A', }, - endColumn: 29, - line: 7, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2851,12 +2851,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 21, + column: 14, data: { name: 'A', }, - endColumn: 22, - line: 7, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2873,12 +2873,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 21, + column: 14, data: { name: 'A', }, - endColumn: 22, - line: 7, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2895,12 +2895,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'A', }, - endColumn: 42, - line: 4, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2920,12 +2920,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'A', }, - endColumn: 42, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -2943,21 +2943,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'A', }, - endColumn: 42, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'B', }, - endColumn: 38, - line: 9, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -2981,30 +2981,30 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'A', }, - endColumn: 42, - line: 6, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'B', }, - endColumn: 38, - line: 14, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, { - column: 37, + column: 14, data: { name: 'C', }, - endColumn: 38, - line: 15, + endColumn: 15, + line: 4, messageId: 'requireTypeExport', }, ], @@ -3026,12 +3026,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'A', }, - endColumn: 42, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -3047,21 +3047,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'A', }, - endColumn: 42, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 25, + column: 14, data: { name: 'B', }, - endColumn: 26, - line: 6, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -3081,12 +3081,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 41, + column: 14, data: { name: 'A', }, - endColumn: 42, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -3102,21 +3102,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 39, + column: 14, data: { - name: 'Key', + name: 'ItemsMap', }, - endColumn: 42, - line: 5, + endColumn: 22, + line: 2, messageId: 'requireTypeExport', }, { - column: 53, + column: 14, data: { - name: 'ItemsMap', + name: 'Key', }, - endColumn: 61, - line: 5, + endColumn: 17, + line: 3, messageId: 'requireTypeExport', }, ], @@ -3133,12 +3133,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 22, + column: 14, data: { name: 'A', }, - endColumn: 23, - line: 4, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -3157,21 +3157,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 23, + column: 14, data: { name: 'A', }, - endColumn: 24, - line: 5, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 23, + column: 14, data: { name: 'B', }, - endColumn: 24, - line: 6, + endColumn: 15, + line: 3, messageId: 'requireTypeExport', }, ], @@ -3186,12 +3186,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 29, + column: 14, data: { name: 'A', }, - endColumn: 30, - line: 6, + endColumn: 15, + line: 4, messageId: 'requireTypeExport', }, ], @@ -3201,25 +3201,25 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { type Apple = 'apple'; type Banana = 'banana'; - export type Fruites = Apple | Banana; + export type Fruits = Apple | Banana; `, errors: [ { - column: 31, + column: 14, data: { name: 'Apple', }, - endColumn: 36, - line: 5, + endColumn: 19, + line: 2, messageId: 'requireTypeExport', }, { - column: 39, + column: 14, data: { name: 'Banana', }, - endColumn: 45, - line: 5, + endColumn: 20, + line: 3, messageId: 'requireTypeExport', }, ], @@ -3239,7 +3239,7 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { name: 'A', }, endColumn: 15, - line: 5, + line: 2, messageId: 'requireTypeExport', }, ], @@ -3259,21 +3259,21 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 27, + column: 14, data: { name: 'A', }, - endColumn: 28, - line: 9, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, { - column: 27, + column: 19, data: { name: 'B', }, - endColumn: 28, - line: 10, + endColumn: 20, + line: 4, messageId: 'requireTypeExport', }, ], @@ -3285,12 +3285,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 33, + column: 14, data: { name: 'A', }, - endColumn: 34, - line: 3, + endColumn: 15, + line: 2, messageId: 'requireTypeExport', }, ], @@ -3307,12 +3307,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 52, + column: 15, data: { - name: 'typeof fruits', + name: 'fruits', }, - endColumn: 65, - line: 4, + endColumn: 21, + line: 2, messageId: 'requireTypeQueryExport', }, ], @@ -3327,12 +3327,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 56, + column: 15, data: { - name: 'typeof fruits', + name: 'fruits', }, - endColumn: 75, - line: 4, + endColumn: 21, + line: 2, messageId: 'requireTypeQueryExport', }, ], @@ -3347,12 +3347,12 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { `, errors: [ { - column: 34, + column: 15, data: { - name: 'typeof fruits', + name: 'fruits', }, - endColumn: 47, - line: 5, + endColumn: 21, + line: 2, messageId: 'requireTypeQueryExport', }, ], From e09b7d9fb83a63b4de24f379487cbc648408100f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 27 Dec 2024 00:40:15 -0500 Subject: [PATCH 85/86] update docs snapshot --- .../require-types-exports.shot | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) 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 index 11bcc9ae95d0..34ec8eeaab0c 100644 --- 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 @@ -3,27 +3,26 @@ exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 1`] = ` "Incorrect -type Arg = string; -type Result = number; - -export function strLength(arg: Arg): Result { - ~~~ "Arg" is used in other exports, so it should also be exported. - ~~~~~~ "Result" is used in other exports, so it should also be exported. - return arg.length; +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 -interface Fruit { - name: string; - color: string; -} +const fruits = { + ~~~~~~ \`typeof fruits\` is used in exports, so \`fruits\` and/or a standalone \`typeof fruits\` should also be exported. + apple: '🍏', + banana: '🍌', +}; -export const getFruitName = (fruit: Fruit) => fruit.name; - ~~~~~ "Fruit" is used in other exports, so it should also be exported. +export const getFruit = (key: keyof typeof fruits) => fruits[key]; " `; @@ -31,37 +30,37 @@ exports[`Validating rule docs require-types-exports.mdx code examples ESLint out "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; - ~~~~~ "Color" is used in other exports, so it should also be exported. " `; exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 4`] = ` "Correct -export type Arg = string; -export type Result = number; - -export function strLength(arg: Arg): Result { - return arg.length; +export interface Fruit { + name: string; + color: string; } + +export const getFruitName = (fruit: Fruit) => fruit.name; " `; exports[`Validating rule docs require-types-exports.mdx code examples ESLint output 5`] = ` "Correct -export interface Fruit { - name: string; - color: string; -} +export const fruits = { + apple: '🍏', + banana: '🍌', +}; -export const getFruitName = (fruit: Fruit) => fruit.name; +export const getFruit = (key: keyof typeof fruits) => fruits[key]; " `; From 8dc96070ee63cb2eb27b2fcb6f7f72d8e1632eac Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 27 Dec 2024 15:20:03 -0500 Subject: [PATCH 86/86] Got up to requiring type info... --- .../src/rules/require-types-exports.ts | 14 ++- .../tests/rules/require-types-exports.test.ts | 111 ++++++++++++++++++ 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/require-types-exports.ts b/packages/eslint-plugin/src/rules/require-types-exports.ts index 78cb3c571b8a..67ab3c005887 100644 --- a/packages/eslint-plugin/src/rules/require-types-exports.ts +++ b/packages/eslint-plugin/src/rules/require-types-exports.ts @@ -10,6 +10,7 @@ import { } 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, @@ -105,6 +106,7 @@ export default createRule({ const { typeQueries, typeReferences } = getVisibleTypesRecursively( node, context.sourceCode, + getParserServices(context, true), ); typeReferences.forEach(checkTypeReference); @@ -222,6 +224,7 @@ interface VisibleTypes { function getVisibleTypesRecursively( node: TSESTree.Node, sourceCode: TSESLint.SourceCode, + services: ParserServices, ): VisibleTypes { const typeReferences = new Set(); const typeQueries = new Set(); @@ -306,7 +309,7 @@ function getVisibleTypesRecursively( collect(child.returnType?.typeAnnotation); if (child.body) { - collectFunctionReturnStatements(child).forEach(collect); + collectFunctionReturnStatements(child, services).forEach(collect); } break; @@ -448,18 +451,15 @@ const exportNodeTypes = new Set([ ]); function isDeclarationExported( - declaration: TSESTree.Node, + declaration: TSESTree.Node & { parent: TSESTree.Node }, services: ParserServices, ) { - if (!declaration.parent) { - return false; - } - 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; @@ -510,11 +510,13 @@ function isReferencedNameInside( 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 && diff --git a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts index fce30ea53776..3a4ba68f6bbc 100644 --- a/packages/eslint-plugin/tests/rules/require-types-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/require-types-exports.test.ts @@ -16,6 +16,25 @@ const ruleTester = new RuleTester({ 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 => {};', @@ -43,9 +62,17 @@ ruleTester.run('require-types-exports', rule, { '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 { @@ -72,6 +99,10 @@ ruleTester.run('require-types-exports', rule, { 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 {} @@ -226,6 +257,10 @@ ruleTester.run('require-types-exports', rule, { 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'; @@ -327,6 +362,13 @@ ruleTester.run('require-types-exports', rule, { 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 { @@ -459,6 +501,18 @@ 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); } `, ], @@ -3009,6 +3063,54 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { }, ], }, + { + 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; @@ -3034,6 +3136,15 @@ export function emitDeprecationWarning(akey: keyof typeof pairs) { line: 2, messageId: 'requireTypeExport', }, + { + column: 14, + data: { + name: 'B', + }, + endColumn: 15, + line: 3, + messageId: 'requireTypeExport', + }, ], }, {