@nestia/core 12.0.0-dev.20260520.1 → 12.0.0-dev.20260521.2

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.
Files changed (30) hide show
  1. package/lib/transform.d.ts +8 -0
  2. package/lib/transform.js +28 -12
  3. package/lib/transform.js.map +1 -1
  4. package/native/cmd/ttsc-nestia/main.go +2 -63
  5. package/native/go.mod +1 -1
  6. package/native/transform/ast.go +32 -0
  7. package/native/{cmd/ttsc-nestia → transform}/build.go +14 -35
  8. package/native/{cmd/ttsc-nestia → transform}/cleanup.go +1 -1
  9. package/native/transform/cleanup_test.go +76 -0
  10. package/native/transform/commonjs_import_alias_test.go +49 -0
  11. package/native/transform/core_dispatch_test.go +127 -0
  12. package/native/{cmd/ttsc-nestia → transform}/core_querify.go +1 -1
  13. package/native/{cmd/ttsc-nestia → transform}/core_transform.go +26 -26
  14. package/native/{cmd/ttsc-nestia → transform}/core_websocket.go +10 -10
  15. package/native/transform/exports.go +13 -0
  16. package/native/{cmd/ttsc-nestia → transform}/path_rewrite.go +1 -1
  17. package/native/transform/path_rewrite_test.go +243 -0
  18. package/native/{cmd/ttsc-nestia → transform}/printer.go +1 -1
  19. package/native/{cmd/ttsc-nestia → transform}/rewrite.go +3 -3
  20. package/native/transform/rewrite_test.go +118 -0
  21. package/native/transform/rewrite_unique_base_test.go +48 -0
  22. package/native/transform/run.go +72 -0
  23. package/native/{cmd/ttsc-nestia → transform}/transform.go +25 -36
  24. package/native/{cmd/ttsc-nestia → transform}/typia_fast.go +1 -1
  25. package/native/{cmd/ttsc-nestia → transform}/typia_replacement.go +1 -1
  26. package/native/transform.cjs +34 -12
  27. package/package.json +8 -7
  28. package/src/transform.ts +39 -20
  29. package/native/cmd/ttsc-nestia/sdk_metadata_json.go +0 -327
  30. package/native/cmd/ttsc-nestia/sdk_transform.go +0 -1541
@@ -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
- var _a;
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;
@@ -1 +1 @@
1
- {"version":3,"file":"transform.js","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":";;;;;;AAAA,0DAA6B;AAC7B,uCAAyC;AAmBzC,MAAM,QAAQ,GAAW,eAAe,EAAE,CAAC;AAC3C,MAAM,OAAO,GAAW,mBAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE/C,0BACE,OAAkC;;IAElC,MAAM,IAAI,SACR,kBAAkB,CAAC,2BAA2B,EAAE,OAAO,CAAC,WAAW,CAAC,mCACpE,gBAAgB,EAAE,CAAC;IACrB,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,mBAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,CAAC;QAC1D,QAAQ,EAAE;YACR,qBAAqB;YACrB,2BAA2B;SAC5B;KACF,CAAC;AACJ,CAAC;kBACc,gBAAgB;AAE/B,SAAS,kBAAkB,CACzB,WAAmB,EACnB,WAAmB;IAEnB,IAAI,CAAC;QACH,OAAO,mBAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E,CAAC;eAAO,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO,mBAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,eAAe;;;IACtB,IACE,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,SAAS;QACxB,UAAU,CAAC,MAAM,KAAK,CAAC;QAEvB,OAAO,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAEvC,MAAM,IAAI,GAAuB,MAAA,IAAI,KAAK,EAAE,CAAC,KAAK,0CAC9C,KAAK,CAAC,IAAI,EACX,IAAI,CACH,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACnC,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACnC,KAAK,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CACvC,CAAC;IACJ,MAAM,OAAO,GAAwC,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,CAC9D,4CAA4C,CAC7C,CAAC;IACF,MAAM,QAAQ,GAAW,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,OAAO,iBAAiB,aAAC,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAG,CAAC,CAAC,mCAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAG,CAAC,CAAC,mCAAI,QAAQ,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAA,wBAAa,EAAC,KAAK,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC;AACf,CAAC"}
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
- var (
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(runSafe(os.Args[1:]))
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-20260510181336-5a7ad21c4d6f
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 main
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
- writeTypiaTransformDiagnostics(stderr, transformDiags, cwd)
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
- writeTypiaTransformDiagnostics(stderr, coreDiags, cwd)
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 typiaTransformDiagnostic struct {
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 typiaTransformDiagnostic) String(cwd string) string {
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 writeTypiaTransformDiagnostics(out io.Writer, diagnostics []typiaTransformDiagnostic, cwd string) {
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 newTypiaTransformDiagnostic(site typiaadapter.CallSite, message string) typiaTransformDiagnostic {
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 typiaTransformDiagnostic{
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, []typiaTransformDiagnostic) {
317
+ ) (int, int, []Diagnostic) {
339
318
  sites := collectNestiaTypiaCallSites(prog.SourceFiles(), prog.Checker)
340
319
  recognized := 0
341
- diagnostics := []typiaTransformDiagnostic{}
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, newTypiaTransformDiagnostic(site, reason))
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, newTypiaTransformDiagnostic(site, "method not covered"))
335
+ diagnostics = append(diagnostics, NewDiagnostic(site, "method not covered"))
357
336
  continue
358
337
  }
359
338
  if err != nil {
360
- diagnostics = append(diagnostics, newTypiaTransformDiagnostic(site, err.Error()))
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 == nestiaCoreKindDecorator {
368
+ if current.Kind == NestiaCoreKindDecorator {
390
369
  insideDecorator = true
391
370
  }
392
371
  if current.Kind == shimast.KindClassDeclaration || current.Kind == shimast.KindClassExpression {
@@ -1,4 +1,4 @@
1
- package main
1
+ package transform
2
2
 
3
3
  import (
4
4
  "fmt"
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- package main
1
+ package transform
2
2
 
3
3
  import (
4
4
  "fmt"