@nestia/core
Version:
Super-fast validation decorators of NestJS
144 lines (132 loc) • 4.85 kB
text/typescript
import path from "path";
import ts from "typescript";
import { INestiaTransformContext } from "../options/INestiaTransformProject";
import { PlainBodyProgrammer } from "../programmers/PlainBodyProgrammer";
import { TypedBodyProgrammer } from "../programmers/TypedBodyProgrammer";
import { TypedFormDataBodyProgrammer } from "../programmers/TypedFormDataBodyProgrammer";
import { TypedHeadersProgrammer } from "../programmers/TypedHeadersProgrammer";
import { TypedParamProgrammer } from "../programmers/TypedParamProgrammer";
import { TypedQueryBodyProgrammer } from "../programmers/TypedQueryBodyProgrammer";
import { TypedQueryProgrammer } from "../programmers/TypedQueryProgrammer";
export namespace ParameterDecoratorTransformer {
export const transform = (props: {
context: INestiaTransformContext;
type: ts.Type;
decorator: ts.Decorator;
}): ts.Decorator => {
//----
// VALIDATIONS
//----
// CHECK DECORATOR
if (!ts.isCallExpression(props.decorator.expression))
return props.decorator;
// SIGNATURE DECLARATION
const declaration: ts.Declaration | undefined =
props.context.checker.getResolvedSignature(
props.decorator.expression,
)?.declaration;
if (declaration === undefined) return props.decorator;
// FILE PATH
const file: string = path.resolve(declaration.getSourceFile().fileName);
if (file.indexOf(LIB_PATH) === -1 && file.indexOf(MONO_PATH) === -1)
return props.decorator;
//----
// TRANSFORMATION
//----
// FIND PROGRAMMER
const programmer: Programmer | undefined =
FUNCTORS[
getName(props.context.checker.getTypeAtLocation(declaration).symbol)
];
if (programmer === undefined) return props.decorator;
// GET TYPE INFO
const typeNode: ts.TypeNode | undefined =
props.context.checker.typeToTypeNode(props.type, undefined, undefined);
if (typeNode === undefined) return props.decorator;
// DO TRANSFORM
return ts.factory.createDecorator(
ts.factory.updateCallExpression(
props.decorator.expression,
props.decorator.expression.expression,
props.decorator.expression.typeArguments,
programmer({
context: props.context,
modulo: props.decorator.expression.expression,
arguments: props.decorator.expression.arguments,
type: props.type,
}),
),
);
};
}
type Programmer = (props: {
context: INestiaTransformContext;
modulo: ts.LeftHandSideExpression;
arguments: readonly ts.Expression[];
type: ts.Type;
}) => readonly ts.Expression[];
const FUNCTORS: Record<string, Programmer> = {
EncryptedBody: (props) =>
props.arguments.length
? props.arguments
: [TypedBodyProgrammer.generate(props)],
TypedBody: (props) =>
props.arguments.length
? props.arguments
: [TypedBodyProgrammer.generate(props)],
TypedHeaders: (props) =>
props.arguments.length
? props.arguments
: [TypedHeadersProgrammer.generate(props)],
TypedParam: (props) =>
props.arguments.length !== 1
? props.arguments
: TypedParamProgrammer.generate(props),
TypedQuery: (props) =>
props.arguments.length
? props.arguments
: [TypedQueryProgrammer.generate(props)],
"TypedQuery.Body": (props) =>
props.arguments.length
? props.arguments
: [TypedQueryBodyProgrammer.generate(props)],
"TypedFormData.Body": (props) =>
props.arguments.length === 0
? [
ts.factory.createIdentifier("undefined"),
TypedFormDataBodyProgrammer.generate(props),
]
: props.arguments.length === 1
? [props.arguments[0], TypedFormDataBodyProgrammer.generate(props)]
: props.arguments,
PlainBody: (props) =>
props.arguments.length
? props.arguments
: [PlainBodyProgrammer.generate(props)],
"WebSocketRoute.Header": (props) =>
props.arguments.length
? props.arguments
: [TypedBodyProgrammer.generate(props)],
"WebSocketRoute.Param": (props) =>
props.arguments.length !== 1
? props.arguments
: TypedParamProgrammer.generate(props),
"WebSocketRoute.Query": (props) =>
props.arguments.length
? props.arguments
: [TypedQueryProgrammer.generate(props)],
};
const LIB_PATH = path.join("@nestia", "core", "lib", "decorators");
const MONO_PATH = path.join("packages", "core", "lib", "decorators");
const getName = (symbol: ts.Symbol): string => {
const parent = symbol.getDeclarations()?.[0]?.parent;
return parent ? exploreName(parent)(symbol.escapedName.toString()) : "__type";
};
const exploreName =
(decl: ts.Node) =>
(name: string): string =>
ts.isModuleBlock(decl)
? exploreName(decl.parent.parent)(
`${decl.parent.name.getFullText().trim()}.${name}`,
)
: name;