@nestia/core 12.0.0-dev.20260520.1 → 12.0.0-dev.20260521.1
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/transform.d.ts +8 -0
- package/lib/transform.js +28 -12
- package/lib/transform.js.map +1 -1
- package/native/cmd/ttsc-nestia/main.go +2 -63
- package/native/go.mod +1 -1
- package/native/transform/ast.go +32 -0
- package/native/{cmd/ttsc-nestia → transform}/build.go +14 -35
- package/native/{cmd/ttsc-nestia → transform}/cleanup.go +1 -1
- package/native/transform/cleanup_test.go +76 -0
- package/native/transform/commonjs_import_alias_test.go +49 -0
- package/native/transform/core_dispatch_test.go +127 -0
- package/native/{cmd/ttsc-nestia → transform}/core_querify.go +1 -1
- package/native/{cmd/ttsc-nestia → transform}/core_transform.go +26 -26
- package/native/{cmd/ttsc-nestia → transform}/core_websocket.go +10 -10
- package/native/transform/exports.go +13 -0
- package/native/{cmd/ttsc-nestia → transform}/path_rewrite.go +1 -1
- package/native/transform/path_rewrite_test.go +243 -0
- package/native/{cmd/ttsc-nestia → transform}/printer.go +1 -1
- package/native/{cmd/ttsc-nestia → transform}/rewrite.go +3 -3
- package/native/transform/rewrite_test.go +118 -0
- package/native/transform/rewrite_unique_base_test.go +48 -0
- package/native/transform/run.go +72 -0
- package/native/{cmd/ttsc-nestia → transform}/transform.go +25 -36
- package/native/{cmd/ttsc-nestia → transform}/typia_fast.go +1 -1
- package/native/{cmd/ttsc-nestia → transform}/typia_replacement.go +1 -1
- package/native/transform.cjs +34 -12
- package/package.json +8 -7
- package/src/transform.ts +39 -20
- package/native/cmd/ttsc-nestia/sdk_metadata_json.go +0 -327
- package/native/cmd/ttsc-nestia/sdk_transform.go +0 -1541
package/lib/transform.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nestia/core` ttsc plugin descriptor.
|
|
3
|
+
*
|
|
4
|
+
* `@nestia/sdk` is not a standalone plugin: its Go transform is declared here
|
|
5
|
+
* as a `contributor` that ttsc statically links into this host binary, and
|
|
6
|
+
* only when `@nestia/sdk` is actually resolvable from the project. A project
|
|
7
|
+
* depending on `@nestia/core` alone never compiles any SDK transform code.
|
|
8
|
+
*/
|
|
1
9
|
export declare function createTtscPlugin(context: ITtscPluginFactoryContext): ITtscPlugin;
|
|
2
10
|
export default createTtscPlugin;
|
package/lib/transform.js
CHANGED
|
@@ -4,34 +4,50 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createTtscPlugin = createTtscPlugin;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
7
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
9
|
const node_url_1 = require("node:url");
|
|
9
10
|
const filename = currentFilename();
|
|
10
11
|
const dirname = node_path_1.default.dirname(filename);
|
|
12
|
+
/**
|
|
13
|
+
* `@nestia/core` ttsc plugin descriptor.
|
|
14
|
+
*
|
|
15
|
+
* `@nestia/sdk` is not a standalone plugin: its Go transform is declared here
|
|
16
|
+
* as a `contributor` that ttsc statically links into this host binary, and
|
|
17
|
+
* only when `@nestia/sdk` is actually resolvable from the project. A project
|
|
18
|
+
* depending on `@nestia/core` alone never compiles any SDK transform code.
|
|
19
|
+
*/
|
|
11
20
|
function createTtscPlugin(context) {
|
|
12
|
-
|
|
13
|
-
const root = (_a = resolvePackageRoot("@nestia/core/package.json", context.projectRoot)) !== null && _a !== void 0 ? _a : inferPackageRoot();
|
|
14
|
-
return {
|
|
21
|
+
const plugin = {
|
|
15
22
|
name: "@nestia/core",
|
|
16
|
-
source: node_path_1.default.resolve(root, "native", "cmd", "ttsc-nestia"),
|
|
17
|
-
composes: [
|
|
18
|
-
"typia/lib/transform",
|
|
19
|
-
"@nestia/sdk/lib/transform",
|
|
20
|
-
],
|
|
23
|
+
source: node_path_1.default.resolve(root(context), "native", "cmd", "ttsc-nestia"),
|
|
24
|
+
composes: ["typia/lib/transform"],
|
|
21
25
|
};
|
|
26
|
+
const sdk = resolveSdkContributorSource(context);
|
|
27
|
+
if (sdk !== null)
|
|
28
|
+
plugin.contributors = [{ name: "sdk", source: sdk }];
|
|
29
|
+
return plugin;
|
|
22
30
|
}
|
|
23
31
|
exports.default = createTtscPlugin;
|
|
32
|
+
function root(context) {
|
|
33
|
+
var _a;
|
|
34
|
+
return ((_a = resolvePackageRoot("@nestia/core/package.json", context.projectRoot)) !== null && _a !== void 0 ? _a : node_path_1.default.resolve(dirname, ".."));
|
|
35
|
+
}
|
|
36
|
+
function resolveSdkContributorSource(context) {
|
|
37
|
+
const manifest = resolvePackageRoot("@nestia/sdk/package.json", context.projectRoot);
|
|
38
|
+
if (manifest === null)
|
|
39
|
+
return null;
|
|
40
|
+
const source = node_path_1.default.resolve(manifest, "native", "sdk");
|
|
41
|
+
return node_fs_1.default.existsSync(source) ? source : null;
|
|
42
|
+
}
|
|
24
43
|
function resolvePackageRoot(packageJson, projectRoot) {
|
|
25
44
|
try {
|
|
26
|
-
return node_path_1.default.dirname(require.resolve(packageJson, { paths: [projectRoot] }));
|
|
45
|
+
return node_path_1.default.dirname(require.resolve(packageJson, { paths: [dirname, projectRoot] }));
|
|
27
46
|
}
|
|
28
47
|
catch (_a) {
|
|
29
48
|
return null;
|
|
30
49
|
}
|
|
31
50
|
}
|
|
32
|
-
function inferPackageRoot() {
|
|
33
|
-
return node_path_1.default.resolve(dirname, "..");
|
|
34
|
-
}
|
|
35
51
|
function currentFilename() {
|
|
36
52
|
var _a;
|
|
37
53
|
var _b, _c;
|
package/lib/transform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.js","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":";;;;;;AAAA,0DAA6B;AAC7B,uCAAyC;
|
|
1
|
+
{"version":3,"file":"transform.js","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,72 +1,11 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
"io"
|
|
6
4
|
"os"
|
|
7
|
-
"runtime/debug"
|
|
8
|
-
)
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
stdout io.Writer = os.Stdout
|
|
12
|
-
stderr io.Writer = os.Stderr
|
|
6
|
+
"github.com/samchon/nestia/packages/core/native/transform"
|
|
13
7
|
)
|
|
14
8
|
|
|
15
9
|
func main() {
|
|
16
|
-
os.Exit(
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// runSafe wraps run() in a panic recovery envelope so that any unexpected
|
|
20
|
-
// panic surfaces as a one-line transform-diagnostic on stderr instead of
|
|
21
|
-
// a multi-line raw Go stack trace. The diagnostic uses the same `<file> -
|
|
22
|
-
// error TS(code): message` shape as every other nestia / typia diagnostic;
|
|
23
|
-
// ttsc reads it via `error.stderr` (`RuntimeCompiler.compile` at
|
|
24
|
-
// `ConfigAnalyzer.ts:170-174`) rather than its structured-diagnostic regex,
|
|
25
|
-
// which is a pre-existing protocol shared by all `nestia.*` codes. The
|
|
26
|
-
// full stack is preserved behind NESTIA_NATIVE_DEBUG_STACK for triage.
|
|
27
|
-
func runSafe(args []string) (code int) {
|
|
28
|
-
defer func() {
|
|
29
|
-
if exp := recover(); exp != nil {
|
|
30
|
-
diag := typiaTransformDiagnostic{
|
|
31
|
-
Code: "nestia.internal.panic",
|
|
32
|
-
Message: fmt.Sprintf("ttsc-nestia panicked: %v", exp),
|
|
33
|
-
}
|
|
34
|
-
writeTypiaTransformDiagnostics(stderr, []typiaTransformDiagnostic{diag}, "")
|
|
35
|
-
if os.Getenv("NESTIA_NATIVE_DEBUG_STACK") != "" {
|
|
36
|
-
fmt.Fprintln(stderr, string(debug.Stack()))
|
|
37
|
-
}
|
|
38
|
-
code = 3
|
|
39
|
-
}
|
|
40
|
-
}()
|
|
41
|
-
return run(args)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
func run(args []string) int {
|
|
45
|
-
if len(args) == 0 {
|
|
46
|
-
return runHelp(nil)
|
|
47
|
-
}
|
|
48
|
-
command := args[0]
|
|
49
|
-
rest := args[1:]
|
|
50
|
-
switch command {
|
|
51
|
-
case "build":
|
|
52
|
-
return runBuild(rest)
|
|
53
|
-
case "check":
|
|
54
|
-
return runCheck(rest)
|
|
55
|
-
case "transform":
|
|
56
|
-
return runTransform(rest)
|
|
57
|
-
case "version":
|
|
58
|
-
fmt.Fprintln(stdout, "ttsc-nestia 0.1.0")
|
|
59
|
-
return 0
|
|
60
|
-
case "help", "-h", "--help":
|
|
61
|
-
return runHelp(rest)
|
|
62
|
-
default:
|
|
63
|
-
fmt.Fprintf(stderr, "ttsc-nestia: unknown command %q\n", command)
|
|
64
|
-
return 2
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func runHelp(args []string) int {
|
|
69
|
-
_ = args
|
|
70
|
-
fmt.Fprintln(stdout, "usage: ttsc-nestia <build|check|transform|version>")
|
|
71
|
-
return 0
|
|
10
|
+
os.Exit(transform.Run(os.Args[1:]))
|
|
72
11
|
}
|
package/native/go.mod
CHANGED
|
@@ -9,7 +9,7 @@ require (
|
|
|
9
9
|
github.com/microsoft/typescript-go/shim/printer v0.0.0
|
|
10
10
|
github.com/microsoft/typescript-go/shim/scanner v0.0.0
|
|
11
11
|
github.com/samchon/ttsc/packages/ttsc v0.0.0
|
|
12
|
-
github.com/samchon/typia/packages/typia/native v0.0.0-
|
|
12
|
+
github.com/samchon/typia/packages/typia/native v0.0.0-20260520144235-368b47d0ea16
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
require (
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strings"
|
|
5
|
+
|
|
6
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// NodeName returns the trimmed identifier text of a named AST node
|
|
10
|
+
// (method, parameter, ...). Empty when the node has no name.
|
|
11
|
+
func NodeName(node *shimast.Node) string {
|
|
12
|
+
if node == nil || node.Name() == nil {
|
|
13
|
+
return ""
|
|
14
|
+
}
|
|
15
|
+
return strings.Trim(node.Name().Text(), "\"'")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// NodeText returns the verbatim source slice spanned by a node.
|
|
19
|
+
func NodeText(node *shimast.Node) string {
|
|
20
|
+
if node == nil {
|
|
21
|
+
return ""
|
|
22
|
+
}
|
|
23
|
+
source, ok := SourceFileText(shimast.GetSourceFileOfNode(node))
|
|
24
|
+
if ok == false {
|
|
25
|
+
return ""
|
|
26
|
+
}
|
|
27
|
+
start, end := node.Pos(), node.End()
|
|
28
|
+
if start < 0 || end > len(source) || start >= end {
|
|
29
|
+
return ""
|
|
30
|
+
}
|
|
31
|
+
return strings.TrimSpace(source[start:end])
|
|
32
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package transform
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"encoding/json"
|
|
@@ -116,7 +116,7 @@ func runBuild(args []string) int {
|
|
|
116
116
|
)
|
|
117
117
|
profileBuildStepCount(profile, "typia-rewrites", started, rewrites.Len())
|
|
118
118
|
if len(transformDiags) > 0 {
|
|
119
|
-
|
|
119
|
+
WriteTypiaTransformDiagnostics(stderr, transformDiags, cwd)
|
|
120
120
|
return 3
|
|
121
121
|
}
|
|
122
122
|
beforeCore := rewrites.Len()
|
|
@@ -126,16 +126,7 @@ func runBuild(args []string) int {
|
|
|
126
126
|
coreDiags := collectNestiaCoreBuildRewrites(prog, plan, rewrites)
|
|
127
127
|
profileBuildStepCount(profile, "core-rewrites", started, rewrites.Len()-beforeCore)
|
|
128
128
|
if len(coreDiags) > 0 {
|
|
129
|
-
|
|
130
|
-
return 3
|
|
131
|
-
}
|
|
132
|
-
if profile {
|
|
133
|
-
started = time.Now()
|
|
134
|
-
}
|
|
135
|
-
sdkRewrites, sdkDiags := collectNestiaSDKBuildRewrites(prog, plan)
|
|
136
|
-
profileBuildStepCount(profile, "sdk-rewrites", started, sdkRewrites.Len())
|
|
137
|
-
if len(sdkDiags) > 0 {
|
|
138
|
-
writeTypiaTransformDiagnostics(stderr, sdkDiags, cwd)
|
|
129
|
+
WriteTypiaTransformDiagnostics(stderr, coreDiags, cwd)
|
|
139
130
|
return 3
|
|
140
131
|
}
|
|
141
132
|
if profile {
|
|
@@ -146,7 +137,6 @@ func runBuild(args []string) int {
|
|
|
146
137
|
|
|
147
138
|
cursors := map[string]int{}
|
|
148
139
|
var nativePatchElapsed time.Duration
|
|
149
|
-
var sdkPatchElapsed time.Duration
|
|
150
140
|
var cleanupElapsed time.Duration
|
|
151
141
|
var writeElapsed time.Duration
|
|
152
142
|
writeFile := shimcompiler.WriteFile(func(fileName, text string, data *shimcompiler.WriteFileData) error {
|
|
@@ -165,16 +155,6 @@ func runBuild(args []string) int {
|
|
|
165
155
|
if profile {
|
|
166
156
|
patchStarted = time.Now()
|
|
167
157
|
}
|
|
168
|
-
patched, err = sdkRewrites.Apply(fileName, patched)
|
|
169
|
-
if profile {
|
|
170
|
-
sdkPatchElapsed += time.Since(patchStarted)
|
|
171
|
-
}
|
|
172
|
-
if err != nil {
|
|
173
|
-
return err
|
|
174
|
-
}
|
|
175
|
-
if profile {
|
|
176
|
-
patchStarted = time.Now()
|
|
177
|
-
}
|
|
178
158
|
patched = cleanupTransformedTextWithRuntimeAliases(patched, rewrites.RuntimeAliasesForOutput(fileName))
|
|
179
159
|
if profile {
|
|
180
160
|
cleanupElapsed += time.Since(patchStarted)
|
|
@@ -191,7 +171,6 @@ func runBuild(args []string) int {
|
|
|
191
171
|
res, eDiags, err := prog.EmitAllRaw(writeFile)
|
|
192
172
|
profileBuildStep(profile, "emit-total", started)
|
|
193
173
|
profileBuildDuration(profile, "emit-native-patch", nativePatchElapsed)
|
|
194
|
-
profileBuildDuration(profile, "emit-sdk-patch", sdkPatchElapsed)
|
|
195
174
|
profileBuildDuration(profile, "emit-cleanup", cleanupElapsed)
|
|
196
175
|
profileBuildDuration(profile, "emit-write", writeElapsed)
|
|
197
176
|
if err != nil {
|
|
@@ -266,7 +245,7 @@ func runCheck(args []string) int {
|
|
|
266
245
|
return 0
|
|
267
246
|
}
|
|
268
247
|
|
|
269
|
-
type
|
|
248
|
+
type Diagnostic struct {
|
|
270
249
|
File string
|
|
271
250
|
Line int
|
|
272
251
|
Column int
|
|
@@ -274,7 +253,7 @@ type typiaTransformDiagnostic struct {
|
|
|
274
253
|
Message string
|
|
275
254
|
}
|
|
276
255
|
|
|
277
|
-
func (d
|
|
256
|
+
func (d Diagnostic) String(cwd string) string {
|
|
278
257
|
file := d.File
|
|
279
258
|
if rel, err := filepath.Rel(cwd, file); err == nil {
|
|
280
259
|
file = rel
|
|
@@ -285,7 +264,7 @@ func (d typiaTransformDiagnostic) String(cwd string) string {
|
|
|
285
264
|
return fmt.Sprintf("%s - error TS(%s): %s", file, d.Code, d.Message)
|
|
286
265
|
}
|
|
287
266
|
|
|
288
|
-
func
|
|
267
|
+
func WriteTypiaTransformDiagnostics(out io.Writer, diagnostics []Diagnostic, cwd string) {
|
|
289
268
|
for _, diag := range diagnostics {
|
|
290
269
|
fmt.Fprintln(out, diag.String(cwd))
|
|
291
270
|
}
|
|
@@ -309,7 +288,7 @@ func profileBuildDuration(enabled bool, name string, elapsed time.Duration) {
|
|
|
309
288
|
}
|
|
310
289
|
}
|
|
311
290
|
|
|
312
|
-
func
|
|
291
|
+
func NewDiagnostic(site typiaadapter.CallSite, message string) Diagnostic {
|
|
313
292
|
line, column := 0, 0
|
|
314
293
|
if site.File != nil && site.Call != nil {
|
|
315
294
|
pos := site.Call.AsNode().Pos()
|
|
@@ -318,7 +297,7 @@ func newTypiaTransformDiagnostic(site typiaadapter.CallSite, message string) typ
|
|
|
318
297
|
line, column = l+1, c+1
|
|
319
298
|
}
|
|
320
299
|
}
|
|
321
|
-
return
|
|
300
|
+
return Diagnostic{
|
|
322
301
|
File: site.FilePath,
|
|
323
302
|
Line: line,
|
|
324
303
|
Column: column,
|
|
@@ -335,10 +314,10 @@ func collectTypiaRewrites(
|
|
|
335
314
|
onlyFile string,
|
|
336
315
|
rewrites *nativeRewriteSet,
|
|
337
316
|
pluginOptions typiaadapter.PluginOptions,
|
|
338
|
-
) (int, int, []
|
|
317
|
+
) (int, int, []Diagnostic) {
|
|
339
318
|
sites := collectNestiaTypiaCallSites(prog.SourceFiles(), prog.Checker)
|
|
340
319
|
recognized := 0
|
|
341
|
-
diagnostics := []
|
|
320
|
+
diagnostics := []Diagnostic{}
|
|
342
321
|
for _, site := range sites {
|
|
343
322
|
if onlyFile != "" && filepath.ToSlash(site.FilePath) != filepath.ToSlash(onlyFile) {
|
|
344
323
|
continue
|
|
@@ -348,16 +327,16 @@ func collectTypiaRewrites(
|
|
|
348
327
|
rel = abs
|
|
349
328
|
}
|
|
350
329
|
if reason := typiaadapter.UnsupportedReason(site); reason != "" {
|
|
351
|
-
diagnostics = append(diagnostics,
|
|
330
|
+
diagnostics = append(diagnostics, NewDiagnostic(site, reason))
|
|
352
331
|
continue
|
|
353
332
|
}
|
|
354
333
|
expr, handled, err := typiaadapter.EmitCallWithOptions(prog, site, pluginOptions)
|
|
355
334
|
if !handled {
|
|
356
|
-
diagnostics = append(diagnostics,
|
|
335
|
+
diagnostics = append(diagnostics, NewDiagnostic(site, "method not covered"))
|
|
357
336
|
continue
|
|
358
337
|
}
|
|
359
338
|
if err != nil {
|
|
360
|
-
diagnostics = append(diagnostics,
|
|
339
|
+
diagnostics = append(diagnostics, NewDiagnostic(site, err.Error()))
|
|
361
340
|
continue
|
|
362
341
|
}
|
|
363
342
|
expr = parenthesizeTypiaReplacement(site, expr)
|
|
@@ -386,7 +365,7 @@ func typiaBuildRewriteSortKey(site typiaadapter.CallSite) int {
|
|
|
386
365
|
insideDecorator := false
|
|
387
366
|
classEnd := 0
|
|
388
367
|
for current := node.Parent; current != nil; current = current.Parent {
|
|
389
|
-
if current.Kind ==
|
|
368
|
+
if current.Kind == NestiaCoreKindDecorator {
|
|
390
369
|
insideDecorator = true
|
|
391
370
|
}
|
|
392
371
|
if current.Kind == shimast.KindClassDeclaration || current.Kind == shimast.KindClassExpression {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strings"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
func TestCleanupTransformedTextWithRuntimeAliases(t *testing.T) {
|
|
9
|
+
input := strings.Join([]string{
|
|
10
|
+
`"use strict";`,
|
|
11
|
+
`const typia_1 = require("typia");`,
|
|
12
|
+
`const value = __typia_transform__isTypeInt32(input);`,
|
|
13
|
+
``,
|
|
14
|
+
}, "\n")
|
|
15
|
+
|
|
16
|
+
output := cleanupTransformedTextWithRuntimeAliases(
|
|
17
|
+
input,
|
|
18
|
+
[]string{"__typia_transform__isTypeInt32"},
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if strings.Contains(output, `require("typia");`) {
|
|
22
|
+
t.Fatalf("unused typia import was not removed:\n%s", output)
|
|
23
|
+
}
|
|
24
|
+
expected := `const { _isTypeInt32: __typia_transform__isTypeInt32 } = require("typia/lib/internal/_isTypeInt32");`
|
|
25
|
+
if strings.Contains(output, expected) == false {
|
|
26
|
+
t.Fatalf("runtime import was not injected:\n%s", output)
|
|
27
|
+
}
|
|
28
|
+
if strings.Index(output, expected) >= strings.Index(output, "const value") {
|
|
29
|
+
t.Fatalf("runtime import was inserted after usage:\n%s", output)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func TestCleanupTransformedTextKeepsReferencedTypiaImport(t *testing.T) {
|
|
34
|
+
input := strings.Join([]string{
|
|
35
|
+
`"use strict";`,
|
|
36
|
+
`const typia_1 = require("typia");`,
|
|
37
|
+
`const value = typia_1.default.assert(input);`,
|
|
38
|
+
``,
|
|
39
|
+
}, "\n")
|
|
40
|
+
|
|
41
|
+
output := cleanupTransformedTextWithRuntimeAliases(input, []string{})
|
|
42
|
+
if strings.Contains(output, `const typia_1 = require("typia");`) == false {
|
|
43
|
+
t.Fatalf("referenced typia import was removed:\n%s", output)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func TestCleanupTransformedTextDoesNotDuplicateRuntimeImport(t *testing.T) {
|
|
48
|
+
existing := `const { _isTypeInt32: __typia_transform__isTypeInt32 } = require("typia/lib/internal/_isTypeInt32");`
|
|
49
|
+
input := strings.Join([]string{
|
|
50
|
+
`"use strict";`,
|
|
51
|
+
existing,
|
|
52
|
+
`const value = __typia_transform__isTypeInt32(input);`,
|
|
53
|
+
``,
|
|
54
|
+
}, "\n")
|
|
55
|
+
|
|
56
|
+
output := cleanupTransformedTextWithRuntimeAliases(
|
|
57
|
+
input,
|
|
58
|
+
[]string{"__typia_transform__isTypeInt32"},
|
|
59
|
+
)
|
|
60
|
+
if strings.Count(output, existing) != 1 {
|
|
61
|
+
t.Fatalf("runtime import was duplicated:\n%s", output)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func TestNativeRewriteSetCollectsRuntimeAliasesFromAppendArguments(t *testing.T) {
|
|
66
|
+
rewrites := newNativeRewriteSet()
|
|
67
|
+
rewrites.Add(nativeRewrite{
|
|
68
|
+
FilePath: "/project/src/controller.ts",
|
|
69
|
+
AppendArguments: []string{`__typia_transform__httpQueryParseURLSearchParams(input)`},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
aliases := rewrites.RuntimeAliasesForOutput("/project/lib/controller.js")
|
|
73
|
+
if len(aliases) != 1 || aliases[0] != "__typia_transform__httpQueryParseURLSearchParams" {
|
|
74
|
+
t.Fatalf("unexpected aliases: %#v", aliases)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
// Verifies commonJSImportAliasBase produces a valid JS identifier base
|
|
6
|
+
// for every module specifier the CommonJS substitution table feeds it.
|
|
7
|
+
//
|
|
8
|
+
// Output forms the `<base>_N.default` substitution map that the native
|
|
9
|
+
// rewrite scan looks up against tsgo's CJS emit. Any input that produces
|
|
10
|
+
// a non-identifier base (leading digit, contains `-`, empty after
|
|
11
|
+
// extension strip) would generate a substitution that never matches
|
|
12
|
+
// tsgo's emit and silently break the rewrite for that import. The
|
|
13
|
+
// reverted P-8 sat on top of this helper — locking the branches now
|
|
14
|
+
// guards the rewrite contract against future emit-side changes.
|
|
15
|
+
//
|
|
16
|
+
// 1. Letters / digits / `_` / `$` survive verbatim.
|
|
17
|
+
// 2. Non-identifier characters (dashes, dots, slashes) become `_`.
|
|
18
|
+
// 3. Empty or pathological bases (`.`, `/`) fall back to `mod`.
|
|
19
|
+
// 4. Leading digits are prefixed with `_` so the base is identifier-safe.
|
|
20
|
+
// 5. Unicode letters survive (Go's `unicode.IsLetter`).
|
|
21
|
+
// 6. File extensions are stripped before sanitization.
|
|
22
|
+
func TestCommonJSImportAliasBaseProducesIdentifierSafeName(t *testing.T) {
|
|
23
|
+
cases := []struct {
|
|
24
|
+
name string
|
|
25
|
+
module string
|
|
26
|
+
want string
|
|
27
|
+
}{
|
|
28
|
+
{"plain package", "typia", "typia"},
|
|
29
|
+
{"scoped package", "@nestia/core", "core"},
|
|
30
|
+
{"dotted package", "lodash.merge", "lodash"},
|
|
31
|
+
{"path with slashes", "@nestia/core/lib/transform", "transform"},
|
|
32
|
+
{"dashes become underscores", "kebab-case-lib", "kebab_case_lib"},
|
|
33
|
+
{"trailing extension stripped", "module.mjs", "module"},
|
|
34
|
+
{"empty becomes mod", "", "mod"},
|
|
35
|
+
{"dot becomes mod", ".", "mod"},
|
|
36
|
+
{"leading digit prefixed with underscore", "1abc", "_1abc"},
|
|
37
|
+
{"underscore start preserved", "_internal", "_internal"},
|
|
38
|
+
{"dollar start preserved", "$jq", "$jq"},
|
|
39
|
+
{"unicode letter preserved", "한글", "한글"},
|
|
40
|
+
{"mixed special chars sanitized", "foo.bar-baz", "foo"},
|
|
41
|
+
}
|
|
42
|
+
for _, tc := range cases {
|
|
43
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
44
|
+
if got := commonJSImportAliasBase(tc.module); got != tc.want {
|
|
45
|
+
t.Fatalf("commonJSImportAliasBase(%q) = %q, want %q", tc.module, got, tc.want)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
// Verifies nestiaCoreParameterKind maps every supported decorator suffix
|
|
6
|
+
// to the programmer kind that drives validator generation.
|
|
7
|
+
//
|
|
8
|
+
// The suffix table is the single source of truth for parameter-decorator
|
|
9
|
+
// dispatch — a silent typo or a missing entry would silently route the
|
|
10
|
+
// affected decorator to the no-op path and emit no validator. The
|
|
11
|
+
// `WebSocketRoute.Header`-as-`TypedBody` and `EncryptedBody`-as-`TypedBody`
|
|
12
|
+
// rows are non-obvious: WebSocket header and encrypted body re-use the
|
|
13
|
+
// TypedBody programmer because their on-the-wire payload shape matches.
|
|
14
|
+
//
|
|
15
|
+
// 1. Every documented suffix returns the matching kind.
|
|
16
|
+
// 2. Multi-segment suffixes (`TypedQuery.Body`, `WebSocketRoute.Param`)
|
|
17
|
+
// resolve only when both tail segments line up.
|
|
18
|
+
// 3. Unknown suffixes return the empty string (the no-op signal).
|
|
19
|
+
func TestNestiaCoreParameterKindDispatch(t *testing.T) {
|
|
20
|
+
cases := []struct {
|
|
21
|
+
name string
|
|
22
|
+
segments []string
|
|
23
|
+
want string
|
|
24
|
+
}{
|
|
25
|
+
{"bare TypedBody", []string{"TypedBody"}, "TypedBody"},
|
|
26
|
+
{"imported core.TypedBody", []string{"core", "TypedBody"}, "TypedBody"},
|
|
27
|
+
{"EncryptedBody maps to TypedBody", []string{"EncryptedBody"}, "TypedBody"},
|
|
28
|
+
{"TypedHeaders", []string{"TypedHeaders"}, "TypedHeaders"},
|
|
29
|
+
{"TypedParam", []string{"TypedParam"}, "TypedParam"},
|
|
30
|
+
{"TypedQuery solo", []string{"TypedQuery"}, "TypedQuery"},
|
|
31
|
+
{"PlainBody", []string{"PlainBody"}, "PlainBody"},
|
|
32
|
+
{"TypedQuery.Body", []string{"TypedQuery", "Body"}, "TypedQueryBody"},
|
|
33
|
+
{"TypedFormData.Body", []string{"TypedFormData", "Body"}, "TypedFormDataBody"},
|
|
34
|
+
{"WebSocketRoute.Header maps to TypedBody", []string{"WebSocketRoute", "Header"}, "TypedBody"},
|
|
35
|
+
{"WebSocketRoute.Param maps to TypedParam", []string{"WebSocketRoute", "Param"}, "TypedParam"},
|
|
36
|
+
{"WebSocketRoute.Query maps to TypedQuery", []string{"WebSocketRoute", "Query"}, "TypedQuery"},
|
|
37
|
+
{"unknown decorator", []string{"TypedSomethingElse"}, ""},
|
|
38
|
+
{"empty segments", []string{}, ""},
|
|
39
|
+
{"TypedQuery.Other is not TypedQueryBody", []string{"TypedQuery", "Other"}, ""},
|
|
40
|
+
{"WebSocketRoute alone is not a parameter", []string{"WebSocketRoute"}, ""},
|
|
41
|
+
}
|
|
42
|
+
for _, tc := range cases {
|
|
43
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
44
|
+
if got := nestiaCoreParameterKind(tc.segments); got != tc.want {
|
|
45
|
+
t.Fatalf("nestiaCoreParameterKind(%v) = %q, want %q", tc.segments, got, tc.want)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Verifies nestiaCoreMethodKind dispatches HTTP-method decorators to the
|
|
52
|
+
// correct route programmer kind.
|
|
53
|
+
//
|
|
54
|
+
// The two-position lookup (`[..., ROOT, VERB]`) keeps `EncryptedRoute.Post`
|
|
55
|
+
// distinct from `TypedQuery.Body` — both have two tail segments but only
|
|
56
|
+
// the former is a route. The verb whitelist (Get/Post/Patch/Put/Delete)
|
|
57
|
+
// pins the contract: any new HTTP verb would have to be added explicitly,
|
|
58
|
+
// preventing accidental dispatch on a typo like `Posts`.
|
|
59
|
+
//
|
|
60
|
+
// 1. Each HTTP verb under `TypedRoute` or `EncryptedRoute` returns `TypedRoute`.
|
|
61
|
+
// 2. Each HTTP verb under `TypedQuery` returns `TypedQueryRoute`.
|
|
62
|
+
// 3. Unknown roots or unknown verbs return the empty string.
|
|
63
|
+
// 4. Single-segment or empty segment lists return the empty string.
|
|
64
|
+
func TestNestiaCoreMethodKindDispatch(t *testing.T) {
|
|
65
|
+
cases := []struct {
|
|
66
|
+
name string
|
|
67
|
+
segments []string
|
|
68
|
+
want string
|
|
69
|
+
}{
|
|
70
|
+
{"TypedRoute.Get", []string{"TypedRoute", "Get"}, "TypedRoute"},
|
|
71
|
+
{"TypedRoute.Post", []string{"TypedRoute", "Post"}, "TypedRoute"},
|
|
72
|
+
{"TypedRoute.Patch", []string{"TypedRoute", "Patch"}, "TypedRoute"},
|
|
73
|
+
{"TypedRoute.Put", []string{"TypedRoute", "Put"}, "TypedRoute"},
|
|
74
|
+
{"TypedRoute.Delete", []string{"TypedRoute", "Delete"}, "TypedRoute"},
|
|
75
|
+
{"EncryptedRoute.Post", []string{"EncryptedRoute", "Post"}, "TypedRoute"},
|
|
76
|
+
{"TypedQuery.Get", []string{"TypedQuery", "Get"}, "TypedQueryRoute"},
|
|
77
|
+
{"imported core.TypedRoute.Get", []string{"core", "TypedRoute", "Get"}, "TypedRoute"},
|
|
78
|
+
{"unknown verb", []string{"TypedRoute", "Head"}, ""},
|
|
79
|
+
{"unknown root", []string{"SomethingElse", "Get"}, ""},
|
|
80
|
+
{"single segment", []string{"Get"}, ""},
|
|
81
|
+
{"empty segments", []string{}, ""},
|
|
82
|
+
{"verb-only suffix", []string{"TypedQuery", "Body"}, ""},
|
|
83
|
+
}
|
|
84
|
+
for _, tc := range cases {
|
|
85
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
86
|
+
if got := nestiaCoreMethodKind(tc.segments); got != tc.want {
|
|
87
|
+
t.Fatalf("nestiaCoreMethodKind(%v) = %q, want %q", tc.segments, got, tc.want)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Verifies nestiaCoreSegmentsHaveSuffix matches the suffix slice against
|
|
94
|
+
// the trailing positions of the segment slice.
|
|
95
|
+
//
|
|
96
|
+
// The helper underpins every decorator-dispatch lookup. Off-by-one or
|
|
97
|
+
// length-clamping bugs would silently mismatch `core.TypedQuery.Body`
|
|
98
|
+
// against the suffix `["TypedQuery"]` — a regression flagged by the
|
|
99
|
+
// existing decorator routing tests, but cheap to anchor at the helper
|
|
100
|
+
// level too.
|
|
101
|
+
//
|
|
102
|
+
// 1. Exact length match returns true.
|
|
103
|
+
// 2. Shorter segments than the suffix never match.
|
|
104
|
+
// 3. Suffix tail of a longer segment list matches; prefix tail does not.
|
|
105
|
+
func TestNestiaCoreSegmentsHaveSuffix(t *testing.T) {
|
|
106
|
+
cases := []struct {
|
|
107
|
+
name string
|
|
108
|
+
segments []string
|
|
109
|
+
suffix []string
|
|
110
|
+
want bool
|
|
111
|
+
}{
|
|
112
|
+
{"exact match", []string{"TypedBody"}, []string{"TypedBody"}, true},
|
|
113
|
+
{"suffix of longer", []string{"core", "TypedBody"}, []string{"TypedBody"}, true},
|
|
114
|
+
{"two-segment suffix", []string{"core", "TypedQuery", "Body"}, []string{"TypedQuery", "Body"}, true},
|
|
115
|
+
{"suffix longer than segments", []string{"TypedBody"}, []string{"core", "TypedBody"}, false},
|
|
116
|
+
{"mismatched tail", []string{"core", "TypedBody"}, []string{"TypedRoute"}, false},
|
|
117
|
+
{"empty suffix", []string{"core", "TypedBody"}, []string{}, true},
|
|
118
|
+
{"both empty", []string{}, []string{}, true},
|
|
119
|
+
}
|
|
120
|
+
for _, tc := range cases {
|
|
121
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
122
|
+
if got := nestiaCoreSegmentsHaveSuffix(tc.segments, tc.suffix); got != tc.want {
|
|
123
|
+
t.Fatalf("nestiaCoreSegmentsHaveSuffix(%v, %v) = %v, want %v", tc.segments, tc.suffix, got, tc.want)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|