@nestia/core 12.0.0-dev.20260601.1 → 12.0.0-dev.20260612.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.
- package/LICENSE +21 -21
- package/MIGRATION.md +169 -169
- package/README.md +93 -93
- package/lib/adaptors/McpAdaptor.d.ts +75 -0
- package/lib/adaptors/McpAdaptor.js +257 -0
- package/lib/adaptors/McpAdaptor.js.map +1 -0
- package/lib/adaptors/WebSocketAdaptor.js +4 -4
- package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
- package/lib/decorators/McpRoute.d.ts +69 -0
- package/lib/decorators/McpRoute.js +58 -0
- package/lib/decorators/McpRoute.js.map +1 -0
- package/lib/decorators/TypedParam.js +4 -4
- package/lib/decorators/TypedParam.js.map +1 -1
- package/lib/decorators/TypedRoute.js +1 -1
- package/lib/decorators/TypedRoute.js.map +1 -1
- package/lib/decorators/internal/IMcpRouteReflect.d.ts +2 -0
- package/lib/decorators/internal/IMcpRouteReflect.js +3 -0
- package/lib/decorators/internal/IMcpRouteReflect.js.map +1 -0
- package/lib/decorators/internal/get_path_and_querify.js +4 -4
- package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
- package/lib/decorators/internal/get_path_and_stringify.js +4 -4
- package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
- package/lib/decorators/internal/load_controller.js +34 -65
- package/lib/decorators/internal/load_controller.js.map +1 -1
- package/lib/decorators/internal/validate_request_body.js +4 -4
- package/lib/decorators/internal/validate_request_body.js.map +1 -1
- package/lib/decorators/internal/validate_request_form_data.js +4 -4
- package/lib/decorators/internal/validate_request_form_data.js.map +1 -1
- package/lib/decorators/internal/validate_request_headers.js +4 -4
- package/lib/decorators/internal/validate_request_headers.js.map +1 -1
- package/lib/decorators/internal/validate_request_query.js +4 -4
- package/lib/decorators/internal/validate_request_query.js.map +1 -1
- package/lib/module.d.ts +2 -0
- package/lib/module.js +2 -0
- package/lib/module.js.map +1 -1
- package/native/cmd/ttsc-nestia/main.go +11 -11
- package/native/go.mod +32 -32
- package/native/go.sum +54 -54
- package/native/plugin/plan.go +102 -102
- package/native/transform/ast.go +32 -32
- package/native/transform/build.go +380 -444
- package/native/transform/cleanup.go +408 -408
- package/native/transform/contributor.go +97 -68
- package/native/transform/core_querify.go +231 -227
- package/native/transform/core_transform.go +1996 -1713
- package/native/transform/core_websocket.go +115 -115
- package/native/transform/exports.go +13 -13
- package/native/transform/mcp_transform.go +414 -0
- package/native/transform/node_transform.go +357 -0
- package/native/transform/path_rewrite.go +285 -285
- package/native/transform/printer.go +244 -244
- package/native/transform/rewrite.go +668 -662
- package/native/transform/run.go +73 -73
- package/native/transform/transform.go +336 -403
- package/native/transform/typia_fast.go +352 -326
- package/native/transform/typia_replacement.go +24 -24
- package/native/transform.cjs +43 -43
- package/package.json +15 -8
- package/src/adaptors/McpAdaptor.ts +276 -0
- package/src/adaptors/WebSocketAdaptor.ts +429 -429
- package/src/decorators/DynamicModule.ts +44 -44
- package/src/decorators/EncryptedBody.ts +97 -97
- package/src/decorators/EncryptedController.ts +40 -40
- package/src/decorators/EncryptedModule.ts +98 -98
- package/src/decorators/EncryptedRoute.ts +213 -213
- package/src/decorators/HumanRoute.ts +21 -21
- package/src/decorators/McpRoute.ts +154 -0
- package/src/decorators/NoTransformConfigurationError.ts +40 -40
- package/src/decorators/PlainBody.ts +76 -76
- package/src/decorators/SwaggerCustomizer.ts +97 -97
- package/src/decorators/SwaggerExample.ts +180 -180
- package/src/decorators/TypedBody.ts +57 -57
- package/src/decorators/TypedException.ts +147 -147
- package/src/decorators/TypedFormData.ts +187 -187
- package/src/decorators/TypedHeaders.ts +66 -66
- package/src/decorators/TypedParam.ts +77 -77
- package/src/decorators/TypedQuery.ts +234 -234
- package/src/decorators/TypedRoute.ts +198 -196
- package/src/decorators/WebSocketRoute.ts +242 -242
- package/src/decorators/doNotThrowTransformError.ts +5 -5
- package/src/decorators/internal/EncryptedConstant.ts +2 -2
- package/src/decorators/internal/IMcpRouteReflect.ts +40 -0
- package/src/decorators/internal/IWebSocketRouteReflect.ts +23 -23
- package/src/decorators/internal/get_path_and_querify.ts +94 -94
- package/src/decorators/internal/get_path_and_stringify.ts +110 -110
- package/src/decorators/internal/get_text_body.ts +16 -16
- package/src/decorators/internal/headers_to_object.ts +11 -11
- package/src/decorators/internal/is_request_body_undefined.ts +12 -12
- package/src/decorators/internal/load_controller.ts +91 -76
- package/src/decorators/internal/route_error.ts +43 -43
- package/src/decorators/internal/validate_request_body.ts +64 -64
- package/src/decorators/internal/validate_request_form_data.ts +67 -67
- package/src/decorators/internal/validate_request_headers.ts +76 -76
- package/src/decorators/internal/validate_request_query.ts +83 -83
- package/src/index.ts +5 -5
- package/src/module.ts +25 -23
- package/src/options/IRequestBodyValidator.ts +20 -20
- package/src/options/IRequestFormDataProps.ts +27 -27
- package/src/options/IRequestHeadersValidator.ts +22 -22
- package/src/options/IRequestQueryValidator.ts +20 -20
- package/src/options/IResponseBodyQuerifier.ts +25 -25
- package/src/options/IResponseBodyStringifier.ts +30 -30
- package/src/transform.ts +101 -101
- package/src/typings/Creator.ts +3 -3
- package/src/typings/get-function-location.d.ts +7 -7
- package/src/utils/ArrayUtil.ts +7 -7
- package/src/utils/ExceptionManager.ts +115 -115
- package/src/utils/Singleton.ts +16 -16
- package/src/utils/SourceFinder.ts +54 -54
- package/src/utils/VersioningStrategy.ts +27 -27
- package/native/transform/cleanup_test.go +0 -76
- package/native/transform/commonjs_import_alias_test.go +0 -49
- package/native/transform/core_dispatch_test.go +0 -127
- package/native/transform/path_rewrite_test.go +0 -243
- package/native/transform/rewrite_test.go +0 -118
- 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
|
-
}
|