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', + }, ], }, { pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy