Skip to content

Commit

Permalink
fix nested types in functions
Browse files Browse the repository at this point in the history
  • Loading branch information
StyleShit committed Apr 24, 2024
1 parent 2cb3455 commit 1812e37
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 226 deletions.
240 changes: 14 additions & 226 deletions packages/eslint-plugin/src/rules/require-types-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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);
}
}
}
Expand All @@ -72,8 +70,7 @@ export default createRule<[], MessageIds>({
declaration: TSESTree.ArrowFunctionExpression;
},
): void {
checkFunctionParamsTypes(node.declaration);
checkFunctionReturnType(node.declaration);
checkFunctionTypes(node.declaration);
}

function visitDefaultExportedIdentifier(
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -355,7 +143,7 @@ export default createRule<[], MessageIds>({
}

return {
'ImportDeclaration[importKind="type"] ImportSpecifier, ImportSpecifier[importKind="type"]':
'ImportDeclaration ImportSpecifier, ImportSpecifier':
collectImportedTypes,

'ExportNamedDeclaration TSTypeAliasDeclaration, ExportNamedDeclaration TSInterfaceDeclaration':
Expand Down
48 changes: 48 additions & 0 deletions packages/eslint-plugin/tests/rules/require-types-exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -1594,6 +1600,48 @@ ruleTester.run('require-types-exports', rule, {
],
},

{
code: `
type Arg1 = number;
type Arg2 = boolean;
type Ret = string;
export declare function f<T extends Arg2>(
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;
Expand Down

0 comments on commit 1812e37

Please sign in to comment.