typia
Version:
Superfast runtime validators with only one line
221 lines (211 loc) • 4.94 kB
text/typescript
import ts from "typescript";
import { Metadata } from "../../../schemas/metadata/Metadata";
import { ArrayUtil } from "../../../utils/ArrayUtil";
import { TypeFactory } from "../../TypeFactory";
export const iterate_metadata_native =
(checker: ts.TypeChecker) =>
(meta: Metadata, type: ts.Type): boolean => {
const validator = validate(checker)(type);
const name: string = TypeFactory.getFullName(checker)(
type,
type.getSymbol(),
);
const simple = SIMPLES.get(name);
if (simple && validator(simple)) {
ArrayUtil.set(meta.natives, name, (str) => str);
return true;
}
for (const generic of GENERICS)
if (
name.substring(0, generic.name.length) === generic.name &&
validator(generic)
) {
ArrayUtil.set(meta.natives, generic.name ?? name, (str) => str);
return true;
}
return false;
};
const validate =
(checker: ts.TypeChecker) => (type: ts.Type) => (info: IClassInfo) =>
(info.methods ?? []).every((method) => {
const returnType = TypeFactory.getReturnType(checker)(type)(method.name);
return (
returnType !== null &&
checker.typeToString(returnType) === method.return
);
}) &&
(info.properties ?? []).every((property) => {
const prop = checker.getPropertyOfType(type, property.name);
const propType = prop?.valueDeclaration
? checker.getTypeAtLocation(prop?.valueDeclaration)
: undefined;
return (
propType !== undefined &&
checker.typeToString(propType) === property.type
);
});
const getBinaryProps = (className: string): IClassInfo => ({
name: className,
methods: [
...["indexOf", "lastIndexOf"].map((name) => ({
name,
return: "number",
})),
...["some", "every"].map((name) => ({
name,
return: "boolean",
})),
...["join", "toLocaleString"].map((name) => ({
name,
return: "string",
})),
...["reverse", "slice", "subarray"].map((name) => ({
name,
return: className,
})),
],
properties: ["BYTES_PER_ELEMENT", "length", "byteLength", "byteOffset"].map(
(name) => ({
name,
type: "number",
}),
),
});
const SIMPLES: Map<string, IClassInfo> = new Map([
[
"Date",
{
methods: ["getTime", "getFullYear", "getMonth", "getMinutes"].map(
(name) => ({
name,
return: "number",
}),
),
},
],
[
"Boolean",
{
methods: [
{
name: "valueOf",
return: "boolean",
},
],
},
],
[
"Number",
{
methods: [
...["toFixed", "toExponential", "toPrecision"].map((name) => ({
name,
return: "string",
})),
{ name: "valueOf", return: "number" },
],
},
],
[
"String",
{
methods: [
"charAt",
"concat",
"valueOf",
"trim",
"replace",
"substring",
].map((name) => ({ name, return: "string" })),
},
],
...[
"Uint8Array",
"Uint8ClampedArray",
"Uint16Array",
"Uint32Array",
"BigUint64Array",
"Int8Array",
"Int16Array",
"Int32Array",
"BigInt64Array",
"Float32Array",
"Float64Array",
].map((name) => [name, getBinaryProps(name)] as const),
...["ArrayBuffer", "SharedArrayBuffer"].map((className) => {
const info: IClassInfo = {
methods: [{ name: "slice", return: className }],
properties: [{ name: "byteLength", type: "number" }],
};
return [className, info] as const;
}),
...["Blob", "File"].map(
(className) =>
[
className,
{
methods: [
{ name: "arrayBuffer", return: "Promise<ArrayBuffer>" },
{ name: "slice", return: "Blob" },
{ name: "text", return: "Promise<string>" },
],
properties: [
{ name: "size", type: "number" },
{ name: "type", type: "string" },
],
},
] satisfies [string, IClassInfo],
),
[
"DataView",
{
methods: [
"getFloat32",
"getFloat64",
"getInt8",
"getInt16",
"getInt32",
"getUint8",
"getUint16",
"getUint32",
].map((name) => ({
name,
return: "number",
})),
},
],
[
"RegExp",
{
methods: [
{
name: "test",
return: "boolean",
},
],
},
],
]);
const GENERICS: Array<IClassInfo & { name: string }> = [
"WeakMap",
"WeakSet",
].map((name) => ({
name,
methods: ["has", "delete"].map((name) => ({
name,
return: "boolean",
})),
}));
interface IClassInfo {
name?: string;
methods?: IMethod[];
properties?: IProperty[];
}
interface IProperty {
name: string;
type: string;
}
interface IMethod {
name: string;
return: string;
}