@matatbread/typia
Version:
Superfast runtime validators with only one line
255 lines (246 loc) • 5.54 kB
text/typescript
import ts from "typescript";
import { MetadataNative } from "../../../schemas/metadata/MetadataNative";
import { ArrayUtil } from "../../../utils/ArrayUtil";
import { TypeFactory } from "../../TypeFactory";
import { IMetadataIteratorProps } from "./IMetadataIteratorProps";
export const iterate_metadata_native = (
props: IMetadataIteratorProps,
): boolean => {
const name: string = TypeFactory.getFullName({
checker: props.checker,
type: props.type,
symbol: props.type.getSymbol(),
});
const simple: IClassInfo | undefined = SIMPLES.get(name);
if (
simple !== undefined &&
validate({
checker: props.checker,
type: props.type,
info: simple,
})
) {
ArrayUtil.take(
props.metadata.natives,
(native) => native.name === name,
() =>
MetadataNative.create({
name,
tags: [],
}),
);
return true;
}
for (const generic of GENERICS)
if (
name.substring(0, generic.name.length) === generic.name &&
validate({
checker: props.checker,
type: props.type,
info: generic,
})
) {
ArrayUtil.take(
props.metadata.natives,
(native) => native.name === name,
() =>
MetadataNative.create({
name: generic.name ?? name,
tags: [],
}),
);
return true;
}
return false;
};
const validate = (props: {
checker: ts.TypeChecker;
type: ts.Type;
info: IClassInfo;
}) =>
(props.info.methods ?? []).every((method) => {
const returnType = TypeFactory.getReturnTypeOfClassMethod({
checker: props.checker,
class: props.type,
function: method.name,
});
return (
returnType !== null &&
props.checker.typeToString(returnType) === method.return
);
}) &&
(props.info.properties ?? []).every((property) => {
const prop = props.checker.getPropertyOfType(props.type, property.name);
const propType = prop?.valueDeclaration
? props.checker.getTypeAtLocation(prop?.valueDeclaration)
: undefined;
return (
propType !== undefined &&
props.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;
}