Skip to content

Commit

Permalink
Tried #1251, but no way to support glob pattern in the generation mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
samchon committed Sep 13, 2024
1 parent b421278 commit 777cd2b
Show file tree
Hide file tree
Showing 28 changed files with 375 additions and 57 deletions.
2 changes: 1 addition & 1 deletion benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@
"suppress-warnings": "^1.0.2",
"tstl": "^3.0.0",
"uuid": "^9.0.1",
"typia": "../typia-6.10.1-dev.20240913.tgz"
"typia": "../typia-6.10.1-dev.20240914.tgz"
}
}
2 changes: 1 addition & 1 deletion errors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
"typescript": "^5.3.2"
},
"dependencies": {
"typia": "../typia-6.10.1-dev.20240913.tgz"
"typia": "../typia-6.10.1-dev.20240914.tgz"
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typia",
"version": "6.10.1-dev.20240913",
"version": "6.10.1-dev.20240914",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -70,6 +70,7 @@
"@samchon/openapi": "^1.0.0",
"commander": "^10.0.0",
"comment-json": "^4.2.3",
"glob": "^7.2.0",
"inquirer": "^8.2.5",
"package-manager-detector": "^0.2.0",
"randexp": "^0.5.3"
Expand All @@ -82,6 +83,7 @@
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/glob": "^7.2.0",
"@types/inquirer": "^8.2.5",
"@types/node": "^18.15.12",
"@types/ts-expose-internals": "npm:[email protected]",
Expand Down
4 changes: 2 additions & 2 deletions packages/typescript-json/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typescript-json",
"version": "6.10.0",
"version": "6.10.1-dev.20240914",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -63,7 +63,7 @@
},
"homepage": "https://typia.io",
"dependencies": {
"typia": "6.10.0"
"typia": "6.10.1-dev.20240914"
},
"peerDependencies": {
"typescript": ">=4.8.0 <5.7.0"
Expand Down
4 changes: 2 additions & 2 deletions src/executable/TypiaGenerateWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export namespace TypiaGenerateWizard {
action,
) => {
// PREPARE ASSETS
command.option("--input [path]", "input directory");
command.option("--input [path]", "input directory or pattern");
command.option("--output [directory]", "output directory");
command.option("--project [project]", "tsconfig.json file location");

Expand Down Expand Up @@ -68,7 +68,7 @@ export namespace TypiaGenerateWizard {
};

return action(async (options) => {
options.input ??= await input("input")("input directory");
options.input ??= await input("input")("input directory or pattern");
options.output ??= await input("output")("output directory");
options.project ??= await configure();
return options as IArguments;
Expand Down
56 changes: 56 additions & 0 deletions src/executable/setup/SourceFinder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import fs from "fs";
import glob from "glob";
import path from "path";

export namespace SourceFinder {
export interface IProps {
exclude?: string[];
include: string[];
filter: (location: string) => boolean;
}
export const find = async (props: IProps): Promise<string[]> => {
const dict: Set<string> = new Set();

await emplace(props.filter)(props.include)((str) => dict.add(str));
if (props.exclude?.length)
await emplace(props.filter)(props.exclude)((str) => dict.delete(str));

return [...dict];
};

const emplace =
(filter: (file: string) => boolean) =>
(input: string[]) =>
async (closure: (location: string) => void): Promise<void> => {
for (const pattern of input) {
for (const file of await _Glob(path.resolve(pattern))) {
const stats: fs.Stats = await fs.promises.stat(file);
if (stats.isDirectory() === true)
await iterate(filter)(closure)(file);
else if (stats.isFile() && filter(file)) closure(file);
}
}
};

const iterate =
(filter: (location: string) => boolean) =>
(closure: (location: string) => void) =>
async (location: string): Promise<void> => {
const directory: string[] = await fs.promises.readdir(location);
for (const file of directory) {
const next: string = path.resolve(`${location}/${file}`);
const stats: fs.Stats = await fs.promises.stat(next);

if (stats.isDirectory() === true) await iterate(filter)(closure)(next);
else if (stats.isFile() && filter(next)) closure(next);
}
};

const _Glob = (pattern: string): Promise<string[]> =>
new Promise((resolve, reject) => {
glob(pattern, (err, matches) => {
if (err) reject(err);
else resolve(matches.map((str) => path.resolve(str)));
});
});
}
96 changes: 48 additions & 48 deletions src/programmers/TypiaProgrammer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ts from "typescript";

import { ImportTransformer } from "../transformers/ImportTransformer";

import { SourceFinder } from "../executable/setup/SourceFinder";
import transform from "../transform";

export namespace TypiaProgrammer {
Expand All @@ -14,25 +15,31 @@ export namespace TypiaProgrammer {
}

export const build = async (props: TypiaProgrammer.IProps): Promise<void> => {
props.input = path.resolve(props.input);
props.output = path.resolve(props.output);

if ((await is_directory(props.input)) === false)
throw new URIError(
"Error on TypiaGenerator.generate(): input path is not a directory.",
const files: string[] = await SourceFinder.find({
include: [props.input],
filter: (location: string) =>
(location.endsWith(".ts") && !location.endsWith("d.ts")) ||
(location.endsWith(".tsx") && !location.endsWith("d.tsx")),
});
if (files.length === 0)
throw new Error(
"Error on TypiaGenerator.generate(): no file to generate.",
);
else if (fs.existsSync(props.output) === false)
await fs.promises.mkdir(props.output, { recursive: true });
else if ((await is_directory(props.output)) === false) {
else if ((await isDirectory(props.output)) === false) {
const parent: string = path.join(props.output, "..");
if ((await is_directory(parent)) === false)
throw new URIError(
if ((await isDirectory(parent)) === false)
throw new Error(
"Error on TypiaGenerator.generate(): output path is not a directory.",
);
await fs.promises.mkdir(props.output);
}

// CREATE PROGRAM
const base: string = (await isDirectory(props.input))
? path.resolve(props.input)
: getBaseDirectory(files);
const { options: compilerOptions } = ts.parseJsonConfigFileContent(
ts.readConfigFile(props.project, ts.sys.readFile).config,
{
Expand All @@ -43,15 +50,7 @@ export namespace TypiaProgrammer {
},
path.dirname(props.project),
);

const program: ts.Program = ts.createProgram(
await (async () => {
const container: string[] = [];
await gather(props)(container)(props.input)(props.output);
return container;
})(),
compilerOptions,
);
const program: ts.Program = ts.createProgram(files, compilerOptions);

// DO TRANSFORM
const diagnostics: ts.Diagnostic[] = [];
Expand All @@ -61,10 +60,10 @@ export namespace TypiaProgrammer {
.filter(
(file) =>
!file.isDeclarationFile &&
path.resolve(file.fileName).indexOf(props.input) !== -1,
path.resolve(file.fileName).indexOf(base) !== -1,
),
[
ImportTransformer.transform(props.input)(props.output),
ImportTransformer.transform(base)(props.output),
transform(
program,
((compilerOptions.plugins as any[]) ?? []).find(
Expand Down Expand Up @@ -111,47 +110,48 @@ export namespace TypiaProgrammer {
if (diagnostics.length) process.exit(-1);

// ARCHIVE TRANSFORMED FILES
const directories: Set<string> = new Set();
const printer: ts.Printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
});
for (const file of result.transformed) {
const to: string = path
.resolve(file.fileName)
.replace(props.input, props.output);

.replace(base, props.output);
const content: string = printer.printFile(file);

const parents: string[] = to.split(path.sep);
for (let i = 1; i < parents.length; i++) {
const parent: string = parents.slice(0, i).join(path.sep);
if (directories.has(parent)) continue;
try {
await fs.promises.mkdir(parent);
} catch {}
}
await fs.promises.writeFile(to, content, "utf8");
}
};

const is_directory = async (current: string): Promise<boolean> => {
const stat: fs.Stats = await fs.promises.stat(current);
return stat.isDirectory();
const isDirectory = async (current: string): Promise<boolean> => {
try {
const stat: fs.Stats = await fs.promises.stat(current);
return stat.isDirectory();
} catch {
return false;
}
};

const gather =
(props: IProps) =>
(container: string[]) =>
(from: string) =>
async (to: string) => {
if (from === props.output) return;
else if (fs.existsSync(to) === false) await fs.promises.mkdir(to);

for (const file of await fs.promises.readdir(from)) {
const next: string = path.join(from, file);
const stat: fs.Stats = await fs.promises.stat(next);

if (stat.isDirectory()) {
await gather(props)(container)(next)(path.join(to, file));
continue;
} else if (is_supported_extension(file)) container.push(next);
}
};
const getBaseDirectory = (files: string[]): string => {
const splitPaths: string[][] = files.map((p) => p.split(path.sep));
const minLength: number = Math.min(...splitPaths.map((p) => p.length));
let commonPath: string[] = [];

const is_supported_extension = (filename: string): boolean => {
return (
(filename.endsWith(".ts") && !filename.endsWith(".d.ts")) ||
(filename.endsWith(".tsx") && !filename.endsWith(".d.tsx"))
);
for (let i = 0; i < minLength; i++) {
const currentSegment: string = splitPaths[0]![i]!;
if (splitPaths.every((p) => p[i] === currentSegment))
commonPath.push(currentSegment);
else break;
}
return commonPath.length > 0 ? commonPath.join(path.sep) : "";
};
}
2 changes: 1 addition & 1 deletion test-esm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@
"typescript": "^5.4.5"
},
"dependencies": {
"typia": "../typia-6.10.1-dev.20240913.tgz"
"typia": "../typia-6.10.1-dev.20240914.tgz"
}
}
2 changes: 2 additions & 0 deletions test-generate/generated/directory/1/1/file11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import typia from "typia";
export const equalsFile = (() => { return (input: any, _exceptionable: boolean = true): input is File => input instanceof File; })();
23 changes: 23 additions & 0 deletions test-generate/generated/directory/1/file1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import typia from "typia";
export const validateFile = (() => { const __is = (input: any): input is File => input instanceof File; let errors: any; let $report: any; return (input: any): typia.IValidation<File> => {
if (false === __is(input)) {
errors = [];
$report = (typia.createValidate as any).report(errors);
((input: any, _path: string, _exceptionable: boolean = true) => input instanceof File || $report(true, {
path: _path + "",
expected: "File",
value: input
}))(input, "$input", true);
const success = 0 === errors.length;
return {
success,
errors,
data: success ? input : undefined
} as any;
}
return {
success: true,
errors: [],
data: input
} as any;
}; })();
12 changes: 12 additions & 0 deletions test-generate/generated/directory/2/file2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import typia from "typia";
export const assertFile = (() => { const $guard = (typia.createAssert as any).guard; const __is = (input: any): input is File => input instanceof File; let _errorFactory: any; return (input: any, errorFactory?: (p: import("typia").TypeGuardError.IProps) => Error): File => {
if (false === __is(input)) {
_errorFactory = errorFactory;
((input: any, _path: string, _exceptionable: boolean = true) => input instanceof File || $guard(true, {
path: _path + "",
expected: "File",
value: input
}, _errorFactory))(input, "$input", true);
}
return input;
}; })();
2 changes: 2 additions & 0 deletions test-generate/generated/directory/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import typia from "typia";
export const isFile = (() => { return (input: any): input is File => input instanceof File; })();
21 changes: 21 additions & 0 deletions test-generate/generated/glob/private/private.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createAssert } from "typia";
import { IPrivate } from "./private.interface";
export const isPrivate = (() => { const $guard = (createAssert as any).guard; const $io0 = (input: any): boolean => "number" === typeof input.value; const $ao0 = (input: any, _path: string, _exceptionable: boolean = true): boolean => "number" === typeof input.value || $guard(_exceptionable, {
path: _path + ".value",
expected: "number",
value: input.value
}, _errorFactory); const __is = (input: any): input is IPrivate => "object" === typeof input && null !== input && $io0(input); let _errorFactory: any; return (input: any, errorFactory?: (p: import("typia").TypeGuardError.IProps) => Error): IPrivate => {
if (false === __is(input)) {
_errorFactory = errorFactory;
((input: any, _path: string, _exceptionable: boolean = true) => ("object" === typeof input && null !== input || $guard(true, {
path: _path + "",
expected: "IPrivate",
value: input
}, _errorFactory)) && $ao0(input, _path + "", true) || $guard(true, {
path: _path + "",
expected: "IPrivate",
value: input
}, _errorFactory))(input, "$input", true);
}
return input;
}; })();
3 changes: 3 additions & 0 deletions test-generate/generated/glob/private/private.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface IPrivate {
value: number;
}
21 changes: 21 additions & 0 deletions test-generate/generated/glob/protected/protected.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createAssert } from "typia";
import { IProtected } from "./protected.interface";
export const assertProtected = (() => { const $guard = (createAssert as any).guard; const $io0 = (input: any): boolean => "string" === typeof input.id; const $ao0 = (input: any, _path: string, _exceptionable: boolean = true): boolean => "string" === typeof input.id || $guard(_exceptionable, {
path: _path + ".id",
expected: "string",
value: input.id
}, _errorFactory); const __is = (input: any): input is IProtected => "object" === typeof input && null !== input && $io0(input); let _errorFactory: any; return (input: any, errorFactory?: (p: import("typia").TypeGuardError.IProps) => Error): IProtected => {
if (false === __is(input)) {
_errorFactory = errorFactory;
((input: any, _path: string, _exceptionable: boolean = true) => ("object" === typeof input && null !== input || $guard(true, {
path: _path + "",
expected: "IProtected",
value: input
}, _errorFactory)) && $ao0(input, _path + "", true) || $guard(true, {
path: _path + "",
expected: "IProtected",
value: input
}, _errorFactory))(input, "$input", true);
}
return input;
}; })();
3 changes: 3 additions & 0 deletions test-generate/generated/glob/protected/protected.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface IProtected {
id: string;
}
Loading

0 comments on commit 777cd2b

Please sign in to comment.