@nestia/core 12.0.0-dev.20260601.1 → 12.0.0-dev.20260612.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.
Files changed (116) hide show
  1. package/LICENSE +21 -21
  2. package/MIGRATION.md +169 -169
  3. package/README.md +93 -93
  4. package/lib/adaptors/McpAdaptor.d.ts +75 -0
  5. package/lib/adaptors/McpAdaptor.js +257 -0
  6. package/lib/adaptors/McpAdaptor.js.map +1 -0
  7. package/lib/adaptors/WebSocketAdaptor.js +4 -4
  8. package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
  9. package/lib/decorators/McpRoute.d.ts +69 -0
  10. package/lib/decorators/McpRoute.js +58 -0
  11. package/lib/decorators/McpRoute.js.map +1 -0
  12. package/lib/decorators/TypedParam.js +4 -4
  13. package/lib/decorators/TypedParam.js.map +1 -1
  14. package/lib/decorators/TypedRoute.js +1 -1
  15. package/lib/decorators/TypedRoute.js.map +1 -1
  16. package/lib/decorators/internal/IMcpRouteReflect.d.ts +2 -0
  17. package/lib/decorators/internal/IMcpRouteReflect.js +3 -0
  18. package/lib/decorators/internal/IMcpRouteReflect.js.map +1 -0
  19. package/lib/decorators/internal/get_path_and_querify.js +4 -4
  20. package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
  21. package/lib/decorators/internal/get_path_and_stringify.js +4 -4
  22. package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
  23. package/lib/decorators/internal/load_controller.js +34 -65
  24. package/lib/decorators/internal/load_controller.js.map +1 -1
  25. package/lib/decorators/internal/validate_request_body.js +4 -4
  26. package/lib/decorators/internal/validate_request_body.js.map +1 -1
  27. package/lib/decorators/internal/validate_request_form_data.js +4 -4
  28. package/lib/decorators/internal/validate_request_form_data.js.map +1 -1
  29. package/lib/decorators/internal/validate_request_headers.js +4 -4
  30. package/lib/decorators/internal/validate_request_headers.js.map +1 -1
  31. package/lib/decorators/internal/validate_request_query.js +4 -4
  32. package/lib/decorators/internal/validate_request_query.js.map +1 -1
  33. package/lib/module.d.ts +2 -0
  34. package/lib/module.js +2 -0
  35. package/lib/module.js.map +1 -1
  36. package/native/cmd/ttsc-nestia/main.go +11 -11
  37. package/native/go.mod +32 -32
  38. package/native/go.sum +54 -54
  39. package/native/plugin/plan.go +102 -102
  40. package/native/transform/ast.go +32 -32
  41. package/native/transform/build.go +380 -444
  42. package/native/transform/cleanup.go +408 -408
  43. package/native/transform/contributor.go +97 -68
  44. package/native/transform/core_querify.go +231 -227
  45. package/native/transform/core_transform.go +1996 -1713
  46. package/native/transform/core_websocket.go +115 -115
  47. package/native/transform/exports.go +13 -13
  48. package/native/transform/mcp_transform.go +414 -0
  49. package/native/transform/node_transform.go +357 -0
  50. package/native/transform/path_rewrite.go +285 -285
  51. package/native/transform/printer.go +244 -244
  52. package/native/transform/rewrite.go +668 -662
  53. package/native/transform/run.go +73 -73
  54. package/native/transform/transform.go +336 -403
  55. package/native/transform/typia_fast.go +352 -326
  56. package/native/transform/typia_replacement.go +24 -24
  57. package/native/transform.cjs +43 -43
  58. package/package.json +15 -8
  59. package/src/adaptors/McpAdaptor.ts +276 -0
  60. package/src/adaptors/WebSocketAdaptor.ts +429 -429
  61. package/src/decorators/DynamicModule.ts +44 -44
  62. package/src/decorators/EncryptedBody.ts +97 -97
  63. package/src/decorators/EncryptedController.ts +40 -40
  64. package/src/decorators/EncryptedModule.ts +98 -98
  65. package/src/decorators/EncryptedRoute.ts +213 -213
  66. package/src/decorators/HumanRoute.ts +21 -21
  67. package/src/decorators/McpRoute.ts +154 -0
  68. package/src/decorators/NoTransformConfigurationError.ts +40 -40
  69. package/src/decorators/PlainBody.ts +76 -76
  70. package/src/decorators/SwaggerCustomizer.ts +97 -97
  71. package/src/decorators/SwaggerExample.ts +180 -180
  72. package/src/decorators/TypedBody.ts +57 -57
  73. package/src/decorators/TypedException.ts +147 -147
  74. package/src/decorators/TypedFormData.ts +187 -187
  75. package/src/decorators/TypedHeaders.ts +66 -66
  76. package/src/decorators/TypedParam.ts +77 -77
  77. package/src/decorators/TypedQuery.ts +234 -234
  78. package/src/decorators/TypedRoute.ts +198 -196
  79. package/src/decorators/WebSocketRoute.ts +242 -242
  80. package/src/decorators/doNotThrowTransformError.ts +5 -5
  81. package/src/decorators/internal/EncryptedConstant.ts +2 -2
  82. package/src/decorators/internal/IMcpRouteReflect.ts +40 -0
  83. package/src/decorators/internal/IWebSocketRouteReflect.ts +23 -23
  84. package/src/decorators/internal/get_path_and_querify.ts +94 -94
  85. package/src/decorators/internal/get_path_and_stringify.ts +110 -110
  86. package/src/decorators/internal/get_text_body.ts +16 -16
  87. package/src/decorators/internal/headers_to_object.ts +11 -11
  88. package/src/decorators/internal/is_request_body_undefined.ts +12 -12
  89. package/src/decorators/internal/load_controller.ts +91 -76
  90. package/src/decorators/internal/route_error.ts +43 -43
  91. package/src/decorators/internal/validate_request_body.ts +64 -64
  92. package/src/decorators/internal/validate_request_form_data.ts +67 -67
  93. package/src/decorators/internal/validate_request_headers.ts +76 -76
  94. package/src/decorators/internal/validate_request_query.ts +83 -83
  95. package/src/index.ts +5 -5
  96. package/src/module.ts +25 -23
  97. package/src/options/IRequestBodyValidator.ts +20 -20
  98. package/src/options/IRequestFormDataProps.ts +27 -27
  99. package/src/options/IRequestHeadersValidator.ts +22 -22
  100. package/src/options/IRequestQueryValidator.ts +20 -20
  101. package/src/options/IResponseBodyQuerifier.ts +25 -25
  102. package/src/options/IResponseBodyStringifier.ts +30 -30
  103. package/src/transform.ts +101 -101
  104. package/src/typings/Creator.ts +3 -3
  105. package/src/typings/get-function-location.d.ts +7 -7
  106. package/src/utils/ArrayUtil.ts +7 -7
  107. package/src/utils/ExceptionManager.ts +115 -115
  108. package/src/utils/Singleton.ts +16 -16
  109. package/src/utils/SourceFinder.ts +54 -54
  110. package/src/utils/VersioningStrategy.ts +27 -27
  111. package/native/transform/cleanup_test.go +0 -76
  112. package/native/transform/commonjs_import_alias_test.go +0 -49
  113. package/native/transform/core_dispatch_test.go +0 -127
  114. package/native/transform/path_rewrite_test.go +0 -243
  115. package/native/transform/rewrite_test.go +0 -118
  116. package/native/transform/rewrite_unique_base_test.go +0 -48
@@ -1,127 +0,0 @@
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,243 +0,0 @@
1
- package transform
2
-
3
- import "testing"
4
-
5
- // Verifies emittedJavaScriptExtension maps source-file extensions to the
6
- // matching JavaScript output extension that tsgo writes.
7
- //
8
- // The native rewrite scan asks for the post-emit name of every registered
9
- // source. A mismatch here means the rewrite searches for `.js` files when
10
- // the project actually emits `.mjs` (ESM packages) or `.cjs`, and the
11
- // "could not locate <call>" failure path then fires on legitimate
12
- // builds. The case-insensitive switch covers Windows-style mixed-case
13
- // `.MTS` paths some toolchains produce.
14
- //
15
- // 1. Cover the three explicit branches (.mts, .cts, default).
16
- // 2. Cover the case-insensitive variants (.MTS, .CTS).
17
- // 3. Cover the default fallback for unknown / extensionless inputs.
18
- func TestEmittedJavaScriptExtensionMapsSourceToOutput(t *testing.T) {
19
- cases := []struct {
20
- name string
21
- source string
22
- want string
23
- }{
24
- {"plain ts", "/repo/src/index.ts", ".js"},
25
- {"plain tsx", "/repo/src/Component.tsx", ".js"},
26
- {"esm mts", "/repo/src/index.mts", ".mjs"},
27
- {"cjs cts", "/repo/src/index.cts", ".cjs"},
28
- {"uppercase mts", "/repo/src/Index.MTS", ".mjs"},
29
- {"uppercase cts", "/repo/src/Index.CTS", ".cjs"},
30
- {"declaration only", "/repo/src/types.d.ts", ".js"},
31
- {"unknown extension", "/repo/src/data.json", ".js"},
32
- {"extensionless", "/repo/src/README", ".js"},
33
- }
34
- for _, tc := range cases {
35
- t.Run(tc.name, func(t *testing.T) {
36
- if got := emittedJavaScriptExtension(tc.source); got != tc.want {
37
- t.Fatalf("emittedJavaScriptExtension(%q) = %q, want %q", tc.source, got, tc.want)
38
- }
39
- })
40
- }
41
- }
42
-
43
- // Verifies matchPattern strictly matches the literal-pattern branch and
44
- // returns the wildcard capture for star-bearing patterns.
45
- //
46
- // `matchPattern` underpins every `paths` resolver entry — a regression in
47
- // the prefix / suffix slice arithmetic would silently break tsconfig
48
- // `paths` aliasing for any project that uses it. The literal branch must
49
- // reject `pattern !== specifier`; the wildcard branch must demand BOTH
50
- // the prefix and suffix to anchor before extracting the capture.
51
- //
52
- // 1. Literal patterns match only on byte equality.
53
- // 2. Single-wildcard patterns extract the gap between prefix and suffix.
54
- // 3. Mismatched prefix or suffix returns no capture.
55
- func TestMatchPatternLiteralAndWildcardBranches(t *testing.T) {
56
- cases := []struct {
57
- name string
58
- pattern string
59
- input string
60
- want string
61
- matched bool
62
- }{
63
- {"literal match", "@api", "@api", "", true},
64
- {"literal mismatch", "@api", "@api/lib", "", false},
65
- {"wildcard captures middle", "@api/*", "@api/users", "users", true},
66
- {"wildcard captures nested", "@api/lib/*", "@api/lib/sub/file", "sub/file", true},
67
- {"wildcard mismatch prefix", "@api/*", "@other/users", "", false},
68
- {"wildcard mismatch suffix", "@api/*.ts", "@api/users.tsx", "", false},
69
- {"wildcard between prefix and suffix", "src/*/index.ts", "src/foo/index.ts", "foo", true},
70
- {"empty capture allowed", "prefix*suffix", "prefixsuffix", "", true},
71
- {"second wildcard treated as literal", "@api/*/lib/*", "@api/foo/lib/*", "foo", true},
72
- {"second wildcard does not expand", "@api/*/lib/*", "@api/foo/lib/bar", "", false},
73
- }
74
- for _, tc := range cases {
75
- t.Run(tc.name, func(t *testing.T) {
76
- got, ok := matchPattern(tc.pattern, tc.input)
77
- if ok != tc.matched {
78
- t.Fatalf("matchPattern(%q, %q) ok = %v, want %v", tc.pattern, tc.input, ok, tc.matched)
79
- }
80
- if ok && got != tc.want {
81
- t.Fatalf("matchPattern(%q, %q) capture = %q, want %q", tc.pattern, tc.input, got, tc.want)
82
- }
83
- })
84
- }
85
- }
86
-
87
- // Verifies patternRank assigns longer literal-character counts higher rank
88
- // so the longest non-wildcard pattern wins when several match.
89
- //
90
- // tsconfig `paths` resolution sorts candidates by `patternRank` descending
91
- // to break ties — without this ordering the resolver could pick the
92
- // loosest pattern over a more specific one (e.g. `*` over `@api/*`).
93
- //
94
- // 1. A pure-wildcard pattern ranks 0.
95
- // 2. Each non-wildcard character adds 1 regardless of position.
96
- // 3. Multiple wildcards do not double-count literal segments.
97
- func TestPatternRankCountsNonWildcardCharacters(t *testing.T) {
98
- cases := []struct {
99
- pattern string
100
- want int
101
- }{
102
- {"*", 0},
103
- {"@api/*", 5},
104
- {"@api/lib/*", 9},
105
- {"src/*/index.ts", 13},
106
- {"prefix*suffix", 12},
107
- }
108
- for _, tc := range cases {
109
- if got := patternRank(tc.pattern); got != tc.want {
110
- t.Fatalf("patternRank(%q) = %d, want %d", tc.pattern, got, tc.want)
111
- }
112
- }
113
- }
114
-
115
- // Verifies normalizePath collapses redundant POSIX separators and
116
- // resolves `..` segments via filepath.Clean.
117
- //
118
- // The rewrite pipeline keys outputs and sources by their normalized
119
- // path. A regression here would split a single logical source into
120
- // multiple cache entries, defeating `nativeRewriteSet`'s deduplication.
121
- // `filepath.ToSlash` runs after Clean — on Linux it is a no-op for
122
- // already-POSIX input; backslash handling is delegated to the platform.
123
- //
124
- // 1. Empty input returns empty.
125
- // 2. Adjacent separators collapse.
126
- // 3. `..` segments are resolved relative to the preceding directory.
127
- // 4. A leading `./` is stripped (filepath.Clean canonicalization).
128
- func TestNormalizePathProducesPosixSlashes(t *testing.T) {
129
- cases := []struct {
130
- input string
131
- want string
132
- }{
133
- {"", ""},
134
- {"/repo/src/index.ts", "/repo/src/index.ts"},
135
- {"/repo//src///index.ts", "/repo/src/index.ts"},
136
- {"/repo/src/../src/index.ts", "/repo/src/index.ts"},
137
- {"./relative/path", "relative/path"},
138
- }
139
- for _, tc := range cases {
140
- if got := normalizePath(tc.input); got != tc.want {
141
- t.Fatalf("normalizePath(%q) = %q, want %q", tc.input, got, tc.want)
142
- }
143
- }
144
- }
145
-
146
- // Verifies isOutsideRelativePath flags any relative path that escapes the
147
- // root via `..` segments.
148
- //
149
- // `pathRewriter.outputForSource` refuses to map sources that resolve
150
- // outside `rootDir`. Without this guard, a controller imported from a
151
- // monorepo neighbor would emit into the outDir at a relative position
152
- // that collides with an unrelated file.
153
- //
154
- // 1. Bare `..` is outside.
155
- // 2. `../...` prefix is outside.
156
- // 3. Same-or-nested paths are inside.
157
- func TestIsOutsideRelativePathRejectsParentEscapes(t *testing.T) {
158
- cases := []struct {
159
- rel string
160
- want bool
161
- }{
162
- {"..", true},
163
- {"../sibling", true},
164
- {"../../escape", true},
165
- {".", false},
166
- {"nested/file.ts", false},
167
- {"sub/../same.ts", false},
168
- }
169
- for _, tc := range cases {
170
- if got := isOutsideRelativePath(tc.rel); got != tc.want {
171
- t.Fatalf("isOutsideRelativePath(%q) = %v, want %v", tc.rel, got, tc.want)
172
- }
173
- }
174
- }
175
-
176
- // Verifies stripKnownSourceExtension prefers the longest known suffix and
177
- // falls back to filepath.Ext for unknown extensions.
178
- //
179
- // The declaration-file extensions (`.d.ts`, `.d.mts`, `.d.cts`) must be
180
- // matched as a whole — falling through to the simpler `.ts` strip would
181
- // leave the `.d` suffix on the stem and silently produce wrong virtual
182
- // paths for declaration emitters.
183
- //
184
- // 1. `.d.ts` strips the full three-character suffix.
185
- // 2. Each one-extension form (`.ts`, `.tsx`, `.mts`, …) strips cleanly.
186
- // 3. Unknown extensions fall back to filepath.Ext.
187
- // 4. Case-insensitive match on uppercase variants.
188
- func TestStripKnownSourceExtensionPrefersLongestMatch(t *testing.T) {
189
- cases := []struct {
190
- input string
191
- want string
192
- }{
193
- {"types.d.ts", "types"},
194
- {"types.d.mts", "types"},
195
- {"types.d.cts", "types"},
196
- {"index.ts", "index"},
197
- {"index.tsx", "index"},
198
- {"index.mts", "index"},
199
- {"index.cts", "index"},
200
- {"index.js", "index"},
201
- {"data.json", "data"},
202
- {"Index.D.TS", "Index"},
203
- {"README", "README"},
204
- }
205
- for _, tc := range cases {
206
- if got := stripKnownSourceExtension(tc.input); got != tc.want {
207
- t.Fatalf("stripKnownSourceExtension(%q) = %q, want %q", tc.input, got, tc.want)
208
- }
209
- }
210
- }
211
-
212
- // Verifies replaceSourceExtension swaps the known TypeScript extension
213
- // while preserving the rest of the path.
214
- //
215
- // Combined with `emittedJavaScriptExtension`, this drives the source ↔
216
- // output filename mapping the rewrite scan depends on. The declaration
217
- // case is load-bearing: `stripKnownSourceExtension` must prefer the full
218
- // `.d.ts` suffix over a plain `.ts` strip, otherwise `types.d.ts +
219
- // .d.ts` would silently become `types..d.ts` and the declaration
220
- // emitter would write a corrupt path.
221
- //
222
- // 1. `.ts` is replaced with the chosen output extension.
223
- // 2. Declaration suffixes (`.d.ts`) strip wholly before replacement.
224
- // 3. POSIX separators in the input flow through unchanged.
225
- // 4. `.d.ts` + `.d.ts` stays `.d.ts` (pins the longest-match prefer rule).
226
- func TestReplaceSourceExtensionSwapsKnownSuffix(t *testing.T) {
227
- cases := []struct {
228
- input string
229
- ext string
230
- want string
231
- }{
232
- {"src/index.ts", ".js", "src/index.js"},
233
- {"src/index.mts", ".mjs", "src/index.mjs"},
234
- {"src/types.d.ts", ".js", "src/types.js"},
235
- {"src/nested/foo.ts", ".js", "src/nested/foo.js"},
236
- {"src/types.d.ts", ".d.ts", "src/types.d.ts"},
237
- }
238
- for _, tc := range cases {
239
- if got := replaceSourceExtension(tc.input, tc.ext); got != tc.want {
240
- t.Fatalf("replaceSourceExtension(%q, %q) = %q, want %q", tc.input, tc.ext, got, tc.want)
241
- }
242
- }
243
- }
@@ -1,118 +0,0 @@
1
- package transform
2
-
3
- import "testing"
4
-
5
- // Verifies OutputMatchesSourceStem accepts virtual-fs same-stem mirrors and
6
- // rejects single-segment suffix collisions across the source tree.
7
- //
8
- // Regression: a top-level `src/index.ts` produced a one-segment relative
9
- // ("index") that the HasSuffix fallback matched against every output ending
10
- // in `/index.js`. The native rewrite scan then trawled unrelated files for
11
- // typia call sites that only existed in the root entry, raising
12
- // "could not locate typia.reflect.schemas(...) call".
13
- //
14
- // 1. Mirror a src/index virtual fs — must match.
15
- // 2. Compare top-level src/index against nested src/api/health/index — must reject.
16
- // 3. Cover the package src->lib mapping and an unrelated stem pair.
17
- func TestOutputMatchesSourceStem(t *testing.T) {
18
- cases := []struct {
19
- name string
20
- output string
21
- source string
22
- want bool
23
- }{
24
- {
25
- name: "virtual fs mirrors src tree",
26
- output: "/cache/.ttsc/project/1/fs/posix/repo/tests/foo/src/index",
27
- source: "/repo/tests/foo/src/index",
28
- want: true,
29
- },
30
- {
31
- name: "top-level src/index does not match nested src/api/health/index",
32
- output: "/cache/.ttsc/project/1/fs/posix/repo/tests/foo/src/api/functional/health/index",
33
- source: "/repo/tests/foo/src/index",
34
- want: false,
35
- },
36
- {
37
- name: "src/api source matches nested api output",
38
- output: "/cache/.ttsc/project/1/fs/posix/repo/tests/foo/src/api/functional/health/index",
39
- source: "/repo/tests/foo/src/api/functional/health/index",
40
- want: true,
41
- },
42
- {
43
- name: "package src maps to lib output",
44
- output: "/repo/packages/core/lib/decorators/TypedBody",
45
- source: "/repo/packages/core/src/decorators/TypedBody",
46
- want: true,
47
- },
48
- {
49
- name: "unrelated stems",
50
- output: "/repo/packages/core/lib/decorators/TypedBody",
51
- source: "/repo/packages/sdk/src/generates/Foo",
52
- want: false,
53
- },
54
- {
55
- name: "top-level src/index does not match nested src/api/index",
56
- output: "/cache/.ttsc/project/1/fs/posix/repo/tests/foo/src/api/index",
57
- source: "/repo/tests/foo/src/index",
58
- want: false,
59
- },
60
- }
61
- for _, tc := range cases {
62
- got := OutputMatchesSourceStem(tc.output, tc.source)
63
- if got != tc.want {
64
- t.Errorf("%s: OutputMatchesSourceStem(%q, %q) = %v, want %v",
65
- tc.name, tc.output, tc.source, got, tc.want)
66
- }
67
- }
68
- }
69
-
70
- func TestCountNativeArgumentsBasic(t *testing.T) {
71
- cases := []struct {
72
- name string
73
- in string
74
- want int
75
- }{
76
- {"empty", "", 0},
77
- {"single", "x", 1},
78
- {"two simple", "a, b", 2},
79
- {"three with object", "a, { b: 1, c: 2 }, d", 3},
80
- {"comma in string", `"a, b", c`, 2},
81
- {"comma in single quote", `'a, b', c, d`, 3},
82
- {"escaped quote", `"a\"b", c`, 2},
83
- {"comma in template", "`a,b`, c", 2},
84
- {"nested call", "foo(a, b), c", 2},
85
- {"nested array", "[1, 2, 3], y", 2},
86
- }
87
- for _, tc := range cases {
88
- got := countNativeArguments(tc.in)
89
- if got != tc.want {
90
- t.Errorf("%s: countNativeArguments(%q) = %d, want %d", tc.name, tc.in, got, tc.want)
91
- }
92
- }
93
- }
94
-
95
- // Regression: template-literal `${...}` substitutions contain real expressions
96
- // — any '(' / ')' / ',' inside a substitution must be treated as expression
97
- // tokens, not template-string content. Prior implementation skipped the entire
98
- // backtick range as if it were a flat string and miscounted arguments when a
99
- // substitution contained quote-shaped characters or nested templates.
100
- func TestCountNativeArgumentsTemplateSubstitution(t *testing.T) {
101
- cases := []struct {
102
- name string
103
- in string
104
- want int
105
- }{
106
- {"quote-inside-substitution", "`x${\")\"}y`, foo", 2},
107
- {"comma-inside-substitution-grouped", "`x${(1, 2)}y`, foo", 2},
108
- {"nested-template", "`a${`b${1}c`}d`, foo", 2},
109
- {"substitution-with-object", "`x${{ a: 1, b: 2 }}y`, foo", 2},
110
- {"call-inside-substitution", "`x${ f(1, 2) }y`, foo", 2},
111
- }
112
- for _, tc := range cases {
113
- got := countNativeArguments(tc.in)
114
- if got != tc.want {
115
- t.Errorf("%s: countNativeArguments(%q) = %d, want %d", tc.name, tc.in, got, tc.want)
116
- }
117
- }
118
- }
@@ -1,48 +0,0 @@
1
- package transform
2
-
3
- import "testing"
4
-
5
- func TestNativeRewriteSetFindSourceByUniqueBase(t *testing.T) {
6
- set := newNativeRewriteSet()
7
- set.Add(nativeRewrite{
8
- FilePath: "/repo/tests/test-transform-options/src/validate.ts",
9
- Method: "TypedBody",
10
- })
11
-
12
- got, ok := set.findSourceForOutput(
13
- "/repo/tests/test-transform-options/lib/validate-assert/validate.js",
14
- )
15
- if !ok {
16
- t.Fatal("expected unique basename fallback to match source")
17
- }
18
- if got != "/repo/tests/test-transform-options/src/validate.ts" {
19
- t.Fatalf("unexpected source match: %q", got)
20
- }
21
- }
22
-
23
- // Verifies the unique-basename fallback does not match a sibling output that
24
- // lives inside the same `src/` tree as the registered source.
25
- //
26
- // Regression: the fallback used to match any output sharing a basename with
27
- // a registered source, so `src/index.ts` silently absorbed rewrites destined
28
- // for every `src/.../index.ts` neighbor produced by ttsc's virtual fs. The
29
- // symptom was `could not locate typia.*(...)` on unrelated generated files.
30
- //
31
- // 1. Register one rewrite for `/repo/tests/foo/src/index.ts`.
32
- // 2. Ask findSourceForOutput for a sibling output inside the same src tree.
33
- // 3. Expect no match.
34
- func TestNativeRewriteSetFindSourceByUniqueBaseSkipsSourceTreeSiblings(t *testing.T) {
35
- set := newNativeRewriteSet()
36
- set.Add(nativeRewrite{
37
- FilePath: "/repo/tests/foo/src/index.ts",
38
- Method: "schemas",
39
- })
40
-
41
- // Output sits inside the same src/ tree, not in lib/dist/bin/build — it
42
- // must not absorb the rewrite registered for src/index.ts.
43
- if got, ok := set.findSourceForOutput(
44
- "/cache/.ttsc/project/1/fs/posix/repo/tests/foo/src/api/functional/health/index.js",
45
- ); ok {
46
- t.Fatalf("expected no match for source-tree sibling, got %q", got)
47
- }
48
- }