@nestia/core 12.0.0 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/adaptors/McpAdaptor.js +21 -18
- package/lib/adaptors/McpAdaptor.js.map +1 -1
- package/lib/decorators/EncryptedBody.js +2 -7
- package/lib/decorators/EncryptedBody.js.map +1 -1
- package/lib/decorators/EncryptedRoute.js +1 -1
- package/lib/decorators/EncryptedRoute.js.map +1 -1
- package/lib/decorators/McpRoute.js +1 -1
- package/lib/decorators/McpRoute.js.map +1 -1
- package/lib/decorators/NoTransformConfigurationError.js +2 -2
- package/lib/decorators/PlainBody.js +2 -7
- package/lib/decorators/PlainBody.js.map +1 -1
- package/lib/decorators/SwaggerCustomizer.js +2 -2
- package/lib/decorators/SwaggerCustomizer.js.map +1 -1
- package/lib/decorators/SwaggerExample.d.ts +37 -40
- package/lib/decorators/SwaggerExample.js +24 -26
- package/lib/decorators/SwaggerExample.js.map +1 -1
- package/lib/decorators/TypedBody.js +3 -7
- package/lib/decorators/TypedBody.js.map +1 -1
- package/lib/decorators/TypedException.d.ts +1 -1
- package/lib/decorators/TypedFormData.js +2 -7
- package/lib/decorators/TypedFormData.js.map +1 -1
- package/lib/decorators/TypedQuery.js +2 -9
- package/lib/decorators/TypedQuery.js.map +1 -1
- package/lib/decorators/WebSocketRoute.js +1 -1
- package/lib/decorators/WebSocketRoute.js.map +1 -1
- package/lib/decorators/internal/get_path_and_querify.js +1 -1
- package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
- package/lib/decorators/internal/is_media_type.d.ts +1 -0
- package/lib/decorators/internal/is_media_type.js +11 -0
- package/lib/decorators/internal/is_media_type.js.map +1 -0
- package/lib/decorators/internal/validate_request_query.js.map +1 -1
- package/native/go.mod +1 -1
- package/native/go.sum +32 -32
- package/native/plugin/plan.go +17 -0
- package/native/transform/build.go +41 -80
- package/native/transform/contributor.go +4 -64
- package/native/transform/core_transform.go +42 -705
- package/native/transform/node_transform.go +3 -5
- package/native/transform/printer.go +0 -217
- package/native/transform/run.go +29 -0
- package/native/transform/transform.go +54 -99
- package/native/transform/typia_dependency.go +117 -0
- package/native/transform/typia_fast.go +0 -320
- package/native/transform/typia_registry.go +40 -0
- package/package.json +12 -8
- package/src/adaptors/McpAdaptor.ts +29 -21
- package/src/decorators/EncryptedBody.ts +2 -9
- package/src/decorators/EncryptedRoute.ts +1 -1
- package/src/decorators/McpRoute.ts +2 -5
- package/src/decorators/NoTransformConfigurationError.ts +2 -2
- package/src/decorators/PlainBody.ts +2 -9
- package/src/decorators/SwaggerCustomizer.ts +4 -2
- package/src/decorators/SwaggerExample.ts +38 -41
- package/src/decorators/TypedBody.ts +5 -9
- package/src/decorators/TypedException.ts +1 -1
- package/src/decorators/TypedFormData.ts +7 -9
- package/src/decorators/TypedQuery.ts +7 -12
- package/src/decorators/WebSocketRoute.ts +1 -1
- package/src/decorators/internal/get_path_and_querify.ts +1 -1
- package/src/decorators/internal/is_media_type.ts +10 -0
- package/src/decorators/internal/validate_request_query.ts +2 -1
- package/native/transform/cleanup.go +0 -408
- package/native/transform/exports.go +0 -13
- package/native/transform/rewrite.go +0 -668
- package/native/transform/typia_replacement.go +0 -24
|
@@ -1,40 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Attach example values to Swagger documents.
|
|
3
3
|
*
|
|
4
|
-
* `SwaggerExample` is a namespace of decorators that attach example values
|
|
5
|
-
*
|
|
4
|
+
* `SwaggerExample` is a namespace of decorators that attach example values to
|
|
5
|
+
* controller methods (request/response bodies, parameters), so that
|
|
6
6
|
* `@nestia/sdk`'s Swagger generator can populate the `example` / `examples`
|
|
7
7
|
* fields of the generated OpenAPI document.
|
|
8
8
|
*
|
|
9
9
|
* The decorators only affect Swagger document generation. They do not change
|
|
10
10
|
* runtime behavior, validation, or SDK function signatures.
|
|
11
11
|
*
|
|
12
|
+
* @author Jeongho Nam - https://github.com/samchon
|
|
12
13
|
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import core from "@nestia/core";
|
|
16
|
+
* import { Controller } from "@nestjs/common";
|
|
17
|
+
* import typia from "typia";
|
|
13
18
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* @core.TypedBody()
|
|
30
|
-
* input: IBbsArticle.ICreate,
|
|
31
|
-
* ): Promise<IBbsArticle> {
|
|
32
|
-
* // ...
|
|
19
|
+
* @Controller("bbs/articles")
|
|
20
|
+
* export class BbsArticlesController {
|
|
21
|
+
* // Single response example.
|
|
22
|
+
* @core.SwaggerExample.Response(typia.random<IBbsArticle>())
|
|
23
|
+
* @core.TypedRoute.Post()
|
|
24
|
+
* public async create(
|
|
25
|
+
* // Multiple named parameter examples plus a default one.
|
|
26
|
+
* @core.SwaggerExample.Parameter(typia.random<IBbsArticle.ICreate>())
|
|
27
|
+
* @core.SwaggerExample.Parameter("x", typia.random<IBbsArticle.ICreate>())
|
|
28
|
+
* @core.SwaggerExample.Parameter("y", typia.random<IBbsArticle.ICreate>())
|
|
29
|
+
* @core.TypedBody()
|
|
30
|
+
* input: IBbsArticle.ICreate,
|
|
31
|
+
* ): Promise<IBbsArticle> {
|
|
32
|
+
* // ...
|
|
33
|
+
* }
|
|
33
34
|
* }
|
|
34
|
-
*
|
|
35
|
-
* ```
|
|
36
|
-
*
|
|
37
|
-
* @author Jeongho Nam - https://github.com/samchon
|
|
35
|
+
* ```;
|
|
38
36
|
*/
|
|
39
37
|
export namespace SwaggerExample {
|
|
40
38
|
/**
|
|
@@ -45,12 +43,12 @@ export namespace SwaggerExample {
|
|
|
45
43
|
* - {@link Response | `Response(value)`}: registers `value` as the single
|
|
46
44
|
* default `example`.
|
|
47
45
|
* - {@link Response | `Response(key, value)`}: registers `value` under
|
|
48
|
-
* `examples[key]`. May be applied multiple times to attach several
|
|
49
|
-
*
|
|
46
|
+
* `examples[key]`. May be applied multiple times to attach several named
|
|
47
|
+
* examples.
|
|
50
48
|
*
|
|
51
|
-
* Both forms can coexist on the same method — the default `example` and
|
|
52
|
-
*
|
|
53
|
-
*
|
|
49
|
+
* Both forms can coexist on the same method — the default `example` and any
|
|
50
|
+
* number of named `examples` will all surface in the generated Swagger
|
|
51
|
+
* document.
|
|
54
52
|
*
|
|
55
53
|
* @template T Type of the example value (typically the route's response DTO).
|
|
56
54
|
*/
|
|
@@ -68,20 +66,19 @@ export namespace SwaggerExample {
|
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/**
|
|
71
|
-
* Attach an example value to a request parameter (body, path, query, etc.)
|
|
72
|
-
*
|
|
69
|
+
* Attach an example value to a request parameter (body, path, query, etc.) of
|
|
70
|
+
* a controller method.
|
|
73
71
|
*
|
|
74
72
|
* Two forms are supported:
|
|
75
73
|
*
|
|
76
74
|
* - {@link Parameter | `Parameter(value)`}: registers `value` as the single
|
|
77
75
|
* default `example` for that parameter.
|
|
78
76
|
* - {@link Parameter | `Parameter(key, value)`}: registers `value` under
|
|
79
|
-
* `examples[key]`. May be applied multiple times to attach several
|
|
80
|
-
*
|
|
77
|
+
* `examples[key]`. May be applied multiple times to attach several named
|
|
78
|
+
* examples to the same parameter.
|
|
81
79
|
*
|
|
82
|
-
* Apply alongside the actual parameter decorator
|
|
83
|
-
*
|
|
84
|
-
* order does not matter.
|
|
80
|
+
* Apply alongside the actual parameter decorator (`@TypedBody()`,
|
|
81
|
+
* `@TypedParam()`, `@TypedQuery()`, etc.); decorator order does not matter.
|
|
85
82
|
*
|
|
86
83
|
* @template T Type of the example value (typically the parameter's DTO).
|
|
87
84
|
*/
|
|
@@ -100,9 +97,9 @@ export namespace SwaggerExample {
|
|
|
100
97
|
/**
|
|
101
98
|
* Internal storage shape for `SwaggerExample` metadata.
|
|
102
99
|
*
|
|
103
|
-
* Reflects the OpenAPI spec's two example fields: a single default
|
|
104
|
-
*
|
|
105
|
-
*
|
|
100
|
+
* Reflects the OpenAPI spec's two example fields: a single default `example`,
|
|
101
|
+
* and/or a named map `examples`. `index` is used internally to associate
|
|
102
|
+
* parameter examples with the right parameter position.
|
|
106
103
|
*/
|
|
107
104
|
export interface IData<T> {
|
|
108
105
|
examples?: Record<string, T>;
|
|
@@ -163,7 +160,7 @@ const emplaceArrayOfParameters = (
|
|
|
163
160
|
target: Object,
|
|
164
161
|
propertyKey: string | symbol,
|
|
165
162
|
): SwaggerExample.IData<any>[] => {
|
|
166
|
-
const array: SwaggerExample.IData<any>[] | undefined = Reflect.
|
|
163
|
+
const array: SwaggerExample.IData<any>[] | undefined = Reflect.getOwnMetadata(
|
|
167
164
|
"nestia/SwaggerExample/Parameters",
|
|
168
165
|
target,
|
|
169
166
|
propertyKey,
|
|
@@ -7,6 +7,7 @@ import type express from "express";
|
|
|
7
7
|
import type { FastifyRequest } from "fastify";
|
|
8
8
|
|
|
9
9
|
import { IRequestBodyValidator } from "../options/IRequestBodyValidator";
|
|
10
|
+
import { is_media_type } from "./internal/is_media_type";
|
|
10
11
|
import { is_request_body_undefined } from "./internal/is_request_body_undefined";
|
|
11
12
|
import { validate_request_body } from "./internal/validate_request_body";
|
|
12
13
|
|
|
@@ -37,7 +38,10 @@ export function TypedBody<T>(
|
|
|
37
38
|
.getRequest();
|
|
38
39
|
if (is_request_body_undefined(request) && checker(undefined as T) === null)
|
|
39
40
|
return undefined;
|
|
40
|
-
else if (
|
|
41
|
+
else if (
|
|
42
|
+
is_media_type(request.headers["content-type"], "application/json") ===
|
|
43
|
+
false
|
|
44
|
+
)
|
|
41
45
|
throw new BadRequestException(
|
|
42
46
|
`Request body type is not "application/json".`,
|
|
43
47
|
);
|
|
@@ -47,11 +51,3 @@ export function TypedBody<T>(
|
|
|
47
51
|
return request.body;
|
|
48
52
|
})();
|
|
49
53
|
}
|
|
50
|
-
|
|
51
|
-
/** @internal */
|
|
52
|
-
const isApplicationJson = (text?: string): boolean =>
|
|
53
|
-
text !== undefined &&
|
|
54
|
-
text
|
|
55
|
-
.split(";")
|
|
56
|
-
.map((str) => str.trim())
|
|
57
|
-
.some((str) => str === "application/json");
|
|
@@ -84,7 +84,7 @@ export function TypedException<T>(
|
|
|
84
84
|
export function TypedException<T>(
|
|
85
85
|
status: number | "2XX" | "3XX" | "4XX" | "5XX",
|
|
86
86
|
description?: string | undefined,
|
|
87
|
-
): MethodDecorator
|
|
87
|
+
): MethodDecorator & Pick<TypedException.IProps<T>, never>;
|
|
88
88
|
|
|
89
89
|
/** @internal */
|
|
90
90
|
export function TypedException<T>(...args: any[]): MethodDecorator {
|
|
@@ -9,6 +9,7 @@ import type ExpressMulter from "multer";
|
|
|
9
9
|
|
|
10
10
|
import type { IRequestFormDataProps } from "../options/IRequestFormDataProps";
|
|
11
11
|
import { Singleton } from "../utils/Singleton";
|
|
12
|
+
import { is_media_type } from "./internal/is_media_type";
|
|
12
13
|
import { validate_request_form_data } from "./internal/validate_request_form_data";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -95,7 +96,12 @@ export namespace TypedFormData {
|
|
|
95
96
|
): Promise<T> {
|
|
96
97
|
const http: HttpArgumentsHost = context.switchToHttp();
|
|
97
98
|
const request: express.Request = http.getRequest();
|
|
98
|
-
if (
|
|
99
|
+
if (
|
|
100
|
+
is_media_type(
|
|
101
|
+
request.headers["content-type"],
|
|
102
|
+
"multipart/form-data",
|
|
103
|
+
) === false
|
|
104
|
+
)
|
|
99
105
|
throw new BadRequestException(
|
|
100
106
|
`Request body type is not "multipart/form-data".`,
|
|
101
107
|
);
|
|
@@ -177,11 +183,3 @@ const parseFiles =
|
|
|
177
183
|
}),
|
|
178
184
|
);
|
|
179
185
|
};
|
|
180
|
-
|
|
181
|
-
/** @internal */
|
|
182
|
-
const isMultipartFormData = (text?: string): boolean =>
|
|
183
|
-
text !== undefined &&
|
|
184
|
-
text
|
|
185
|
-
.split(";")
|
|
186
|
-
.map((str) => str.trim())
|
|
187
|
-
.some((str) => str === "multipart/form-data");
|
|
@@ -21,6 +21,7 @@ import typia from "typia";
|
|
|
21
21
|
import { IRequestQueryValidator } from "../options/IRequestQueryValidator";
|
|
22
22
|
import { IResponseBodyQuerifier } from "../options/IResponseBodyQuerifier";
|
|
23
23
|
import { get_path_and_querify } from "./internal/get_path_and_querify";
|
|
24
|
+
import { is_media_type } from "./internal/is_media_type";
|
|
24
25
|
import { route_error } from "./internal/route_error";
|
|
25
26
|
import { validate_request_query } from "./internal/validate_request_query";
|
|
26
27
|
|
|
@@ -79,7 +80,12 @@ export namespace TypedQuery {
|
|
|
79
80
|
const request: express.Request | FastifyRequest = context
|
|
80
81
|
.switchToHttp()
|
|
81
82
|
.getRequest();
|
|
82
|
-
if (
|
|
83
|
+
if (
|
|
84
|
+
is_media_type(
|
|
85
|
+
request.headers["content-type"],
|
|
86
|
+
"application/x-www-form-urlencoded",
|
|
87
|
+
) === false
|
|
88
|
+
)
|
|
83
89
|
throw new BadRequestException(
|
|
84
90
|
`Request body type is not "application/x-www-form-urlencoded".`,
|
|
85
91
|
);
|
|
@@ -172,17 +178,6 @@ function tail(url: string): string {
|
|
|
172
178
|
return index === -1 ? "" : url.substring(index + 1);
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
/** @internal */
|
|
176
|
-
function isApplicationQuery(text?: string): boolean {
|
|
177
|
-
return (
|
|
178
|
-
text !== undefined &&
|
|
179
|
-
text
|
|
180
|
-
.split(";")
|
|
181
|
-
.map((str) => str.trim())
|
|
182
|
-
.some((str) => str === "application/x-www-form-urlencoded")
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
181
|
/** @internal */
|
|
187
182
|
class FakeURLSearchParams {
|
|
188
183
|
public constructor(private readonly target: Record<string, string[]>) {}
|
|
@@ -225,7 +225,7 @@ export namespace WebSocketRoute {
|
|
|
225
225
|
value: IWebSocketRouteReflect.IArgument,
|
|
226
226
|
) => {
|
|
227
227
|
const array: IWebSocketRouteReflect.IArgument[] | undefined =
|
|
228
|
-
Reflect.
|
|
228
|
+
Reflect.getOwnMetadata(
|
|
229
229
|
"nestia/WebSocketRoute/Parameters",
|
|
230
230
|
target,
|
|
231
231
|
propertyKey,
|
|
@@ -42,7 +42,7 @@ const take =
|
|
|
42
42
|
const querify = (input: Record<string, any>): URLSearchParams => {
|
|
43
43
|
const output: URLSearchParams = new URLSearchParams();
|
|
44
44
|
for (const [key, value] of Object.entries(input))
|
|
45
|
-
if (
|
|
45
|
+
if (value === undefined) continue;
|
|
46
46
|
else if (Array.isArray(value))
|
|
47
47
|
for (const elem of value) output.append(key, String(elem));
|
|
48
48
|
else output.append(key, String(value));
|
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
package transform
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
"sort"
|
|
6
|
-
"strings"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
func cleanupTransformedText(text string) string {
|
|
10
|
-
return cleanupTransformedTextWithRuntimeAliases(text, nil)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
func cleanupTransformedTextWithRuntimeAliases(text string, aliases []string) string {
|
|
14
|
-
text = removeUnusedTypiaImports(text)
|
|
15
|
-
if aliases == nil {
|
|
16
|
-
return injectCleanupRuntimeImports(text)
|
|
17
|
-
}
|
|
18
|
-
return injectCleanupRuntimeImportsWithAliases(text, aliases)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
func removeUnusedTypiaImports(text string) string {
|
|
22
|
-
prefix := cleanupImportScanPrefix(text)
|
|
23
|
-
if !strings.Contains(prefix, `require("typia")`) &&
|
|
24
|
-
!strings.Contains(prefix, `require('typia')`) &&
|
|
25
|
-
!strings.Contains(prefix, `from "typia"`) &&
|
|
26
|
-
!strings.Contains(prefix, `from 'typia'`) {
|
|
27
|
-
return text
|
|
28
|
-
}
|
|
29
|
-
type removal struct {
|
|
30
|
-
start int
|
|
31
|
-
end int
|
|
32
|
-
}
|
|
33
|
-
removals := []removal{}
|
|
34
|
-
offset := 0
|
|
35
|
-
for offset < len(prefix) {
|
|
36
|
-
next := strings.IndexByte(prefix[offset:], '\n')
|
|
37
|
-
lineEnd := len(prefix)
|
|
38
|
-
nextOffset := len(prefix)
|
|
39
|
-
if next >= 0 {
|
|
40
|
-
lineEnd = offset + next
|
|
41
|
-
nextOffset = lineEnd + 1
|
|
42
|
-
}
|
|
43
|
-
line := strings.TrimSuffix(prefix[offset:lineEnd], "\r")
|
|
44
|
-
if alias, ok := unusedTypiaImportAlias(line); ok &&
|
|
45
|
-
!cleanupIdentifierStillReferenced(text, alias, offset, nextOffset) {
|
|
46
|
-
removals = append(removals, removal{start: offset, end: nextOffset})
|
|
47
|
-
}
|
|
48
|
-
offset = nextOffset
|
|
49
|
-
}
|
|
50
|
-
for i := len(removals) - 1; i >= 0; i-- {
|
|
51
|
-
r := removals[i]
|
|
52
|
-
text = text[:r.start] + text[r.end:]
|
|
53
|
-
}
|
|
54
|
-
return text
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
func unusedTypiaImportAlias(line string) (string, bool) {
|
|
58
|
-
if strings.HasPrefix(line, "const ") {
|
|
59
|
-
index := strings.Index(line, " = ")
|
|
60
|
-
if index < 0 {
|
|
61
|
-
return "", false
|
|
62
|
-
}
|
|
63
|
-
alias := strings.TrimSpace(strings.TrimPrefix(line[:index], "const "))
|
|
64
|
-
if isTypiaImportAlias(alias) == false {
|
|
65
|
-
return "", false
|
|
66
|
-
}
|
|
67
|
-
value := strings.TrimSpace(line[index+3:])
|
|
68
|
-
return alias, value == `__importDefault(require("typia"));` ||
|
|
69
|
-
value == `__importDefault(require('typia'));` ||
|
|
70
|
-
value == `require("typia");` ||
|
|
71
|
-
value == `require('typia');`
|
|
72
|
-
}
|
|
73
|
-
if strings.HasPrefix(line, "import ") && strings.HasSuffix(line, ` from "typia";`) {
|
|
74
|
-
alias := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "import "), ` from "typia";`))
|
|
75
|
-
return alias, isTypiaImportAlias(alias)
|
|
76
|
-
}
|
|
77
|
-
if strings.HasPrefix(line, "import ") && strings.HasSuffix(line, ` from 'typia';`) {
|
|
78
|
-
alias := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "import "), ` from 'typia';`))
|
|
79
|
-
return alias, isTypiaImportAlias(alias)
|
|
80
|
-
}
|
|
81
|
-
return "", false
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
func isTypiaImportAlias(alias string) bool {
|
|
85
|
-
if alias == "typia" {
|
|
86
|
-
return true
|
|
87
|
-
}
|
|
88
|
-
if !strings.HasPrefix(alias, "typia_") {
|
|
89
|
-
return false
|
|
90
|
-
}
|
|
91
|
-
for _, ch := range alias[len("typia_"):] {
|
|
92
|
-
if ch < '0' || ch > '9' {
|
|
93
|
-
return false
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return len(alias) > len("typia_")
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
func cleanupIdentifierStillReferenced(text, alias string, lineStart, lineEnd int) bool {
|
|
100
|
-
return cleanupIdentifierAppears(text[:lineStart], alias) ||
|
|
101
|
-
cleanupIdentifierAppears(text[lineEnd:], alias)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
func cleanupIdentifierAppears(text string, alias string) bool {
|
|
105
|
-
offset := 0
|
|
106
|
-
for {
|
|
107
|
-
index := strings.Index(text[offset:], alias)
|
|
108
|
-
if index < 0 {
|
|
109
|
-
return false
|
|
110
|
-
}
|
|
111
|
-
start := offset + index
|
|
112
|
-
end := start + len(alias)
|
|
113
|
-
if (start == 0 || !isCleanupRuntimeAliasIdentifierByte(text[start-1])) &&
|
|
114
|
-
(end == len(text) || !isCleanupRuntimeAliasIdentifierByte(text[end])) {
|
|
115
|
-
return true
|
|
116
|
-
}
|
|
117
|
-
offset = end
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
func injectCleanupRuntimeImports(text string) string {
|
|
122
|
-
aliases := collectCleanupRuntimeAliases(text)
|
|
123
|
-
return injectCleanupRuntimeImportsWithAliases(text, aliases)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
func injectCleanupRuntimeImportsWithAliases(text string, aliases []string) string {
|
|
127
|
-
if len(aliases) == 0 {
|
|
128
|
-
return text
|
|
129
|
-
}
|
|
130
|
-
esModule := isCleanupESModuleOutput(text)
|
|
131
|
-
existing := collectExistingCleanupRuntimeImports(text)
|
|
132
|
-
imports := make([]string, 0, len(aliases))
|
|
133
|
-
for _, alias := range aliases {
|
|
134
|
-
module := cleanupRuntimeModuleOf(alias)
|
|
135
|
-
if existing[cleanupRuntimeImportKey(alias, module)] {
|
|
136
|
-
continue
|
|
137
|
-
}
|
|
138
|
-
name := cleanupRuntimeNameOf(alias)
|
|
139
|
-
if esModule {
|
|
140
|
-
imports = append(imports, fmt.Sprintf(`import { %s as %s } from %q;`, name, alias, module))
|
|
141
|
-
} else {
|
|
142
|
-
imports = append(imports, fmt.Sprintf(`const { %s: %s } = require(%q);`, name, alias, module))
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if len(imports) == 0 {
|
|
146
|
-
return text
|
|
147
|
-
}
|
|
148
|
-
index := cleanupRuntimeImportInsertionIndex(text, esModule)
|
|
149
|
-
return text[:index] + strings.Join(imports, "\n") + "\n" + text[index:]
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
func collectCleanupRuntimeAliases(text string) []string {
|
|
153
|
-
seen := map[string]bool{}
|
|
154
|
-
offset := 0
|
|
155
|
-
for {
|
|
156
|
-
index := strings.Index(text[offset:], cleanupRuntimeAliasPrefix)
|
|
157
|
-
if index < 0 {
|
|
158
|
-
break
|
|
159
|
-
}
|
|
160
|
-
start := offset + index
|
|
161
|
-
end := start + len(cleanupRuntimeAliasPrefix)
|
|
162
|
-
if start != 0 && isCleanupRuntimeAliasIdentifierByte(text[start-1]) {
|
|
163
|
-
offset = end
|
|
164
|
-
continue
|
|
165
|
-
}
|
|
166
|
-
for end < len(text) && isCleanupRuntimeAliasNameByte(text[end]) {
|
|
167
|
-
end++
|
|
168
|
-
}
|
|
169
|
-
if end != start+len(cleanupRuntimeAliasPrefix) &&
|
|
170
|
-
(end == len(text) || !isCleanupRuntimeAliasIdentifierByte(text[end])) {
|
|
171
|
-
seen[text[start:end]] = true
|
|
172
|
-
}
|
|
173
|
-
offset = end
|
|
174
|
-
}
|
|
175
|
-
return sortCleanupRuntimeAliases(seen)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
func sortCleanupRuntimeAliases(seen map[string]bool) []string {
|
|
179
|
-
aliases := make([]string, 0, len(seen))
|
|
180
|
-
for alias := range seen {
|
|
181
|
-
aliases = append(aliases, alias)
|
|
182
|
-
}
|
|
183
|
-
sort.SliceStable(aliases, func(i, j int) bool {
|
|
184
|
-
left, right := cleanupRuntimeAliasRank(aliases[i]), cleanupRuntimeAliasRank(aliases[j])
|
|
185
|
-
if left != right {
|
|
186
|
-
return left < right
|
|
187
|
-
}
|
|
188
|
-
return aliases[i] < aliases[j]
|
|
189
|
-
})
|
|
190
|
-
return aliases
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const cleanupRuntimeAliasPrefix = "__typia_transform_"
|
|
194
|
-
|
|
195
|
-
func isCleanupRuntimeAliasNameByte(value byte) bool {
|
|
196
|
-
return (value >= 'A' && value <= 'Z') ||
|
|
197
|
-
(value >= 'a' && value <= 'z') ||
|
|
198
|
-
(value >= '0' && value <= '9') ||
|
|
199
|
-
value == '_'
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
func isCleanupRuntimeAliasIdentifierByte(value byte) bool {
|
|
203
|
-
return isCleanupRuntimeAliasNameByte(value) || value == '$'
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
func cleanupRuntimeAliasRank(alias string) int {
|
|
207
|
-
name := strings.TrimPrefix(alias, cleanupRuntimeAliasPrefix)
|
|
208
|
-
switch {
|
|
209
|
-
case strings.HasPrefix(name, "_is"):
|
|
210
|
-
return 100
|
|
211
|
-
case strings.HasPrefix(name, "_assert"):
|
|
212
|
-
return 150
|
|
213
|
-
case strings.HasPrefix(name, "_randomFormat"):
|
|
214
|
-
return 200
|
|
215
|
-
case name == "_randomString":
|
|
216
|
-
return 210
|
|
217
|
-
case name == "_randomInteger":
|
|
218
|
-
return 220
|
|
219
|
-
case name == "_randomNumber":
|
|
220
|
-
return 221
|
|
221
|
-
case strings.HasPrefix(name, "_random"):
|
|
222
|
-
return 230
|
|
223
|
-
case name == "_validateReport":
|
|
224
|
-
return 800
|
|
225
|
-
case name == "_createStandardSchema":
|
|
226
|
-
return 900
|
|
227
|
-
}
|
|
228
|
-
return 500
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
func cleanupRuntimeModuleOf(alias string) string {
|
|
232
|
-
return "typia/lib/internal/" + cleanupRuntimeNameOf(alias)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
func cleanupRuntimeNameOf(alias string) string {
|
|
236
|
-
name := strings.TrimPrefix(alias, cleanupRuntimeAliasPrefix)
|
|
237
|
-
if !strings.HasPrefix(name, "_") {
|
|
238
|
-
name = "_" + name
|
|
239
|
-
}
|
|
240
|
-
return name
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
func collectExistingCleanupRuntimeImports(text string) map[string]bool {
|
|
244
|
-
output := map[string]bool{}
|
|
245
|
-
forEachCleanupImportLine(cleanupImportScanPrefix(text), func(line string) {
|
|
246
|
-
if !strings.Contains(line, cleanupRuntimeAliasPrefix) ||
|
|
247
|
-
!strings.Contains(line, "typia/lib/internal/") {
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
alias, ok := cleanupRuntimeImportAlias(line)
|
|
251
|
-
if !ok {
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
module, ok := cleanupRuntimeImportModule(line)
|
|
255
|
-
if !ok {
|
|
256
|
-
return
|
|
257
|
-
}
|
|
258
|
-
output[cleanupRuntimeImportKey(alias, module)] = true
|
|
259
|
-
})
|
|
260
|
-
return output
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
func cleanupImportScanPrefix(text string) string {
|
|
264
|
-
const limit = 1 << 20
|
|
265
|
-
if len(text) <= limit {
|
|
266
|
-
return text
|
|
267
|
-
}
|
|
268
|
-
return text[:limit]
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
func cleanupRuntimeImportKey(alias string, module string) string {
|
|
272
|
-
return alias + "\x00" + module
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
func forEachCleanupImportLine(text string, iterate func(string)) {
|
|
276
|
-
for len(text) > 0 {
|
|
277
|
-
next := strings.IndexByte(text, '\n')
|
|
278
|
-
line := text
|
|
279
|
-
if next >= 0 {
|
|
280
|
-
line = text[:next]
|
|
281
|
-
text = text[next+1:]
|
|
282
|
-
} else {
|
|
283
|
-
text = ""
|
|
284
|
-
}
|
|
285
|
-
iterate(strings.TrimSuffix(line, "\r"))
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
func cleanupRuntimeImportAlias(line string) (string, bool) {
|
|
290
|
-
switch {
|
|
291
|
-
case strings.HasPrefix(line, "const { "):
|
|
292
|
-
colon := strings.IndexByte(line, ':')
|
|
293
|
-
if colon < 0 {
|
|
294
|
-
return "", false
|
|
295
|
-
}
|
|
296
|
-
end := strings.IndexByte(line[colon+1:], '}')
|
|
297
|
-
if end < 0 {
|
|
298
|
-
return "", false
|
|
299
|
-
}
|
|
300
|
-
return cleanupFirstIdentifier(strings.TrimSpace(line[colon+1 : colon+1+end]))
|
|
301
|
-
case strings.HasPrefix(line, "import { "):
|
|
302
|
-
index := strings.LastIndex(line, " as ")
|
|
303
|
-
if index < 0 {
|
|
304
|
-
return "", false
|
|
305
|
-
}
|
|
306
|
-
end := strings.IndexByte(line[index+4:], '}')
|
|
307
|
-
if end < 0 {
|
|
308
|
-
return "", false
|
|
309
|
-
}
|
|
310
|
-
return cleanupFirstIdentifier(strings.TrimSpace(line[index+4 : index+4+end]))
|
|
311
|
-
case strings.HasPrefix(line, "const "):
|
|
312
|
-
rest := strings.TrimPrefix(line, "const ")
|
|
313
|
-
index := strings.Index(rest, " = require(")
|
|
314
|
-
if index < 0 {
|
|
315
|
-
return "", false
|
|
316
|
-
}
|
|
317
|
-
return cleanupFirstIdentifier(rest[:index])
|
|
318
|
-
case strings.HasPrefix(line, "import * as "):
|
|
319
|
-
rest := strings.TrimPrefix(line, "import * as ")
|
|
320
|
-
index := strings.Index(rest, " from ")
|
|
321
|
-
if index < 0 {
|
|
322
|
-
return "", false
|
|
323
|
-
}
|
|
324
|
-
return cleanupFirstIdentifier(rest[:index])
|
|
325
|
-
default:
|
|
326
|
-
return "", false
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
func cleanupFirstIdentifier(text string) (string, bool) {
|
|
331
|
-
fields := strings.Fields(text)
|
|
332
|
-
if len(fields) == 0 {
|
|
333
|
-
return "", false
|
|
334
|
-
}
|
|
335
|
-
name := strings.Trim(fields[0], " \t{}")
|
|
336
|
-
if !strings.HasPrefix(name, cleanupRuntimeAliasPrefix) {
|
|
337
|
-
return "", false
|
|
338
|
-
}
|
|
339
|
-
return name, true
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
func cleanupRuntimeImportModule(line string) (string, bool) {
|
|
343
|
-
for _, marker := range []string{`require("`, `require('`, ` from "`, ` from '`} {
|
|
344
|
-
index := strings.LastIndex(line, marker)
|
|
345
|
-
if index < 0 {
|
|
346
|
-
continue
|
|
347
|
-
}
|
|
348
|
-
start := index + len(marker)
|
|
349
|
-
quote := marker[len(marker)-1]
|
|
350
|
-
end := strings.IndexByte(line[start:], quote)
|
|
351
|
-
if end < 0 {
|
|
352
|
-
return "", false
|
|
353
|
-
}
|
|
354
|
-
module := line[start : start+end]
|
|
355
|
-
if !strings.HasPrefix(module, "typia/lib/internal/") {
|
|
356
|
-
return "", false
|
|
357
|
-
}
|
|
358
|
-
return module, true
|
|
359
|
-
}
|
|
360
|
-
return "", false
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
func isCleanupESModuleOutput(text string) bool {
|
|
364
|
-
output := false
|
|
365
|
-
forEachCleanupImportLine(cleanupImportScanPrefix(text), func(line string) {
|
|
366
|
-
if strings.HasPrefix(line, "import ") ||
|
|
367
|
-
strings.HasPrefix(line, "import{") ||
|
|
368
|
-
strings.HasPrefix(line, "import*") ||
|
|
369
|
-
strings.HasPrefix(line, "export ") {
|
|
370
|
-
output = true
|
|
371
|
-
}
|
|
372
|
-
})
|
|
373
|
-
return output
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
func cleanupRuntimeImportInsertionIndex(text string, esModule bool) int {
|
|
377
|
-
index := 0
|
|
378
|
-
if strings.HasPrefix(text, "#!") {
|
|
379
|
-
if next := strings.IndexByte(text, '\n'); next >= 0 {
|
|
380
|
-
index = next + 1
|
|
381
|
-
} else {
|
|
382
|
-
return len(text)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if esModule {
|
|
386
|
-
return index
|
|
387
|
-
}
|
|
388
|
-
for {
|
|
389
|
-
next := consumeCleanupRuntimeImportPrefix(text[index:])
|
|
390
|
-
if next == 0 {
|
|
391
|
-
return index
|
|
392
|
-
}
|
|
393
|
-
index += next
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
func consumeCleanupRuntimeImportPrefix(text string) int {
|
|
398
|
-
for _, prefix := range []string{
|
|
399
|
-
"\"use strict\";\n",
|
|
400
|
-
"'use strict';\n",
|
|
401
|
-
"/* @ttsc-rewritten */\n",
|
|
402
|
-
} {
|
|
403
|
-
if strings.HasPrefix(text, prefix) {
|
|
404
|
-
return len(prefix)
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
return 0
|
|
408
|
-
}
|