@nestia/core 11.2.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/MIGRATION.md +169 -0
- package/lib/adaptors/WebSocketAdaptor.js +7 -3
- package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
- package/lib/decorators/DynamicModule.js.map +1 -1
- package/lib/decorators/EncryptedBody.js.map +1 -1
- package/lib/decorators/EncryptedController.js.map +1 -1
- package/lib/decorators/EncryptedModule.js.map +1 -1
- package/lib/decorators/EncryptedRoute.js.map +1 -1
- package/lib/decorators/HumanRoute.js.map +1 -1
- package/lib/decorators/NoTransformConfigurationError.js +5 -2
- package/lib/decorators/NoTransformConfigurationError.js.map +1 -1
- package/lib/decorators/PlainBody.js.map +1 -1
- package/lib/decorators/SwaggerCustomizer.js.map +1 -1
- package/lib/decorators/SwaggerExample.js.map +1 -1
- package/lib/decorators/TypedBody.js.map +1 -1
- package/lib/decorators/TypedException.js.map +1 -1
- package/lib/decorators/TypedFormData.js.map +1 -1
- package/lib/decorators/TypedHeaders.js.map +1 -1
- package/lib/decorators/TypedParam.js +5 -2
- package/lib/decorators/TypedParam.js.map +1 -1
- package/lib/decorators/TypedQuery.js.map +1 -1
- package/lib/decorators/TypedRoute.js.map +1 -1
- package/lib/decorators/WebSocketRoute.js.map +1 -1
- package/lib/decorators/doNotThrowTransformError.js.map +1 -1
- package/lib/decorators/internal/get_path_and_querify.js +5 -2
- package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
- package/lib/decorators/internal/get_path_and_stringify.js +5 -2
- package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
- package/lib/decorators/internal/get_text_body.js.map +1 -1
- package/lib/decorators/internal/headers_to_object.js.map +1 -1
- package/lib/decorators/internal/is_request_body_undefined.js.map +1 -1
- package/lib/decorators/internal/load_controller.js +48 -16
- package/lib/decorators/internal/load_controller.js.map +1 -1
- package/lib/decorators/internal/route_error.js +3 -3
- package/lib/decorators/internal/route_error.js.map +1 -1
- package/lib/decorators/internal/validate_request_body.js +5 -2
- package/lib/decorators/internal/validate_request_body.js.map +1 -1
- package/lib/decorators/internal/validate_request_form_data.js +5 -2
- package/lib/decorators/internal/validate_request_form_data.js.map +1 -1
- package/lib/decorators/internal/validate_request_headers.js +5 -2
- package/lib/decorators/internal/validate_request_headers.js.map +1 -1
- package/lib/decorators/internal/validate_request_query.js +30 -4
- package/lib/decorators/internal/validate_request_query.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/transform.d.ts +10 -5
- package/lib/transform.js +64 -28
- package/lib/transform.js.map +1 -1
- package/lib/utils/ArrayUtil.js.map +1 -1
- package/lib/utils/ExceptionManager.js.map +1 -1
- package/lib/utils/Singleton.js.map +1 -1
- package/lib/utils/SourceFinder.js.map +1 -1
- package/lib/utils/VersioningStrategy.js.map +1 -1
- package/native/cmd/ttsc-nestia/main.go +11 -0
- package/native/go.mod +32 -0
- package/native/go.sum +54 -0
- package/native/plugin/plan.go +102 -0
- package/native/transform/ast.go +32 -0
- package/native/transform/build.go +413 -0
- package/native/transform/cleanup.go +408 -0
- 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/transform/core_querify.go +227 -0
- package/native/transform/core_transform.go +1713 -0
- package/native/transform/core_websocket.go +115 -0
- package/native/transform/exports.go +13 -0
- package/native/transform/path_rewrite.go +285 -0
- package/native/transform/path_rewrite_test.go +243 -0
- package/native/transform/printer.go +244 -0
- package/native/transform/rewrite.go +662 -0
- 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/transform/transform.go +376 -0
- package/native/transform/typia_fast.go +326 -0
- package/native/transform/typia_replacement.go +24 -0
- package/native/transform.cjs +43 -0
- package/package.json +28 -22
- package/src/decorators/NoTransformConfigurationError.ts +5 -2
- package/src/decorators/internal/load_controller.ts +50 -19
- package/src/decorators/internal/validate_request_query.ts +21 -2
- package/src/transform.ts +101 -35
- package/lib/decorators/internal/NoTransformConfigureError.d.ts +0 -1
- package/lib/decorators/internal/NoTransformConfigureError.js +0 -7
- package/lib/decorators/internal/NoTransformConfigureError.js.map +0 -1
- package/lib/options/INestiaTransformOptions.d.ts +0 -13
- package/lib/options/INestiaTransformOptions.js +0 -3
- package/lib/options/INestiaTransformOptions.js.map +0 -1
- package/lib/options/INestiaTransformProject.d.ts +0 -5
- package/lib/options/INestiaTransformProject.js +0 -3
- package/lib/options/INestiaTransformProject.js.map +0 -1
- package/lib/programmers/PlainBodyProgrammer.d.ts +0 -9
- package/lib/programmers/PlainBodyProgrammer.js +0 -58
- package/lib/programmers/PlainBodyProgrammer.js.map +0 -1
- package/lib/programmers/TypedBodyProgrammer.d.ts +0 -9
- package/lib/programmers/TypedBodyProgrammer.js +0 -94
- package/lib/programmers/TypedBodyProgrammer.js.map +0 -1
- package/lib/programmers/TypedFormDataBodyProgrammer.d.ts +0 -9
- package/lib/programmers/TypedFormDataBodyProgrammer.js +0 -65
- package/lib/programmers/TypedFormDataBodyProgrammer.js.map +0 -1
- package/lib/programmers/TypedHeadersProgrammer.d.ts +0 -9
- package/lib/programmers/TypedHeadersProgrammer.js +0 -38
- package/lib/programmers/TypedHeadersProgrammer.js.map +0 -1
- package/lib/programmers/TypedParamProgrammer.d.ts +0 -10
- package/lib/programmers/TypedParamProgrammer.js +0 -32
- package/lib/programmers/TypedParamProgrammer.js.map +0 -1
- package/lib/programmers/TypedQueryBodyProgrammer.d.ts +0 -9
- package/lib/programmers/TypedQueryBodyProgrammer.js +0 -74
- package/lib/programmers/TypedQueryBodyProgrammer.js.map +0 -1
- package/lib/programmers/TypedQueryProgrammer.d.ts +0 -9
- package/lib/programmers/TypedQueryProgrammer.js +0 -75
- package/lib/programmers/TypedQueryProgrammer.js.map +0 -1
- package/lib/programmers/TypedQueryRouteProgrammer.d.ts +0 -9
- package/lib/programmers/TypedQueryRouteProgrammer.js +0 -75
- package/lib/programmers/TypedQueryRouteProgrammer.js.map +0 -1
- package/lib/programmers/TypedRouteProgrammer.d.ts +0 -9
- package/lib/programmers/TypedRouteProgrammer.js +0 -79
- package/lib/programmers/TypedRouteProgrammer.js.map +0 -1
- package/lib/programmers/http/HttpAssertQuerifyProgrammer.d.ts +0 -9
- package/lib/programmers/http/HttpAssertQuerifyProgrammer.js +0 -39
- package/lib/programmers/http/HttpAssertQuerifyProgrammer.js.map +0 -1
- package/lib/programmers/http/HttpIsQuerifyProgrammer.d.ts +0 -9
- package/lib/programmers/http/HttpIsQuerifyProgrammer.js +0 -36
- package/lib/programmers/http/HttpIsQuerifyProgrammer.js.map +0 -1
- package/lib/programmers/http/HttpQuerifyProgrammer.d.ts +0 -9
- package/lib/programmers/http/HttpQuerifyProgrammer.js +0 -50
- package/lib/programmers/http/HttpQuerifyProgrammer.js.map +0 -1
- package/lib/programmers/http/HttpValidateQuerifyProgrammer.d.ts +0 -9
- package/lib/programmers/http/HttpValidateQuerifyProgrammer.js +0 -40
- package/lib/programmers/http/HttpValidateQuerifyProgrammer.js.map +0 -1
- package/lib/programmers/internal/CoreMetadataUtil.d.ts +0 -5
- package/lib/programmers/internal/CoreMetadataUtil.js +0 -19
- package/lib/programmers/internal/CoreMetadataUtil.js.map +0 -1
- package/lib/transformers/FileTransformer.d.ts +0 -5
- package/lib/transformers/FileTransformer.js +0 -80
- package/lib/transformers/FileTransformer.js.map +0 -1
- package/lib/transformers/MethodTransformer.d.ts +0 -8
- package/lib/transformers/MethodTransformer.js +0 -58
- package/lib/transformers/MethodTransformer.js.map +0 -1
- package/lib/transformers/NodeTransformer.d.ts +0 -8
- package/lib/transformers/NodeTransformer.js +0 -24
- package/lib/transformers/NodeTransformer.js.map +0 -1
- package/lib/transformers/ParameterDecoratorTransformer.d.ts +0 -9
- package/lib/transformers/ParameterDecoratorTransformer.js +0 -104
- package/lib/transformers/ParameterDecoratorTransformer.js.map +0 -1
- package/lib/transformers/ParameterTransformer.d.ts +0 -8
- package/lib/transformers/ParameterTransformer.js +0 -37
- package/lib/transformers/ParameterTransformer.js.map +0 -1
- package/lib/transformers/TypedRouteTransformer.d.ts +0 -9
- package/lib/transformers/TypedRouteTransformer.js +0 -68
- package/lib/transformers/TypedRouteTransformer.js.map +0 -1
- package/lib/transformers/WebSocketRouteTransformer.d.ts +0 -9
- package/lib/transformers/WebSocketRouteTransformer.js +0 -72
- package/lib/transformers/WebSocketRouteTransformer.js.map +0 -1
- package/src/decorators/internal/NoTransformConfigureError.ts +0 -2
- package/src/options/INestiaTransformOptions.ts +0 -34
- package/src/options/INestiaTransformProject.ts +0 -10
- package/src/programmers/PlainBodyProgrammer.ts +0 -72
- package/src/programmers/TypedBodyProgrammer.ts +0 -148
- package/src/programmers/TypedFormDataBodyProgrammer.ts +0 -118
- package/src/programmers/TypedHeadersProgrammer.ts +0 -65
- package/src/programmers/TypedParamProgrammer.ts +0 -33
- package/src/programmers/TypedQueryBodyProgrammer.ts +0 -113
- package/src/programmers/TypedQueryProgrammer.ts +0 -115
- package/src/programmers/TypedQueryRouteProgrammer.ts +0 -107
- package/src/programmers/TypedRouteProgrammer.ts +0 -103
- package/src/programmers/http/HttpAssertQuerifyProgrammer.ts +0 -74
- package/src/programmers/http/HttpIsQuerifyProgrammer.ts +0 -77
- package/src/programmers/http/HttpQuerifyProgrammer.ts +0 -110
- package/src/programmers/http/HttpValidateQuerifyProgrammer.ts +0 -78
- package/src/programmers/internal/CoreMetadataUtil.ts +0 -21
- package/src/transformers/FileTransformer.ts +0 -109
- package/src/transformers/MethodTransformer.ts +0 -103
- package/src/transformers/NodeTransformer.ts +0 -23
- package/src/transformers/ParameterDecoratorTransformer.ts +0 -143
- package/src/transformers/ParameterTransformer.ts +0 -57
- package/src/transformers/TypedRouteTransformer.ts +0 -85
- package/src/transformers/WebSocketRouteTransformer.ts +0 -120
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"sort"
|
|
6
|
+
"strings"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func cleanupTransformedText(text string) string {
|
|
10
|
+
return cleanupTransformedTextWithRuntimeAliases(text, nil)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
func cleanupTransformedTextWithRuntimeAliases(text string, aliases []string) string {
|
|
14
|
+
text = removeUnusedTypiaImports(text)
|
|
15
|
+
if aliases == nil {
|
|
16
|
+
return injectCleanupRuntimeImports(text)
|
|
17
|
+
}
|
|
18
|
+
return injectCleanupRuntimeImportsWithAliases(text, aliases)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func removeUnusedTypiaImports(text string) string {
|
|
22
|
+
prefix := cleanupImportScanPrefix(text)
|
|
23
|
+
if !strings.Contains(prefix, `require("typia")`) &&
|
|
24
|
+
!strings.Contains(prefix, `require('typia')`) &&
|
|
25
|
+
!strings.Contains(prefix, `from "typia"`) &&
|
|
26
|
+
!strings.Contains(prefix, `from 'typia'`) {
|
|
27
|
+
return text
|
|
28
|
+
}
|
|
29
|
+
type removal struct {
|
|
30
|
+
start int
|
|
31
|
+
end int
|
|
32
|
+
}
|
|
33
|
+
removals := []removal{}
|
|
34
|
+
offset := 0
|
|
35
|
+
for offset < len(prefix) {
|
|
36
|
+
next := strings.IndexByte(prefix[offset:], '\n')
|
|
37
|
+
lineEnd := len(prefix)
|
|
38
|
+
nextOffset := len(prefix)
|
|
39
|
+
if next >= 0 {
|
|
40
|
+
lineEnd = offset + next
|
|
41
|
+
nextOffset = lineEnd + 1
|
|
42
|
+
}
|
|
43
|
+
line := strings.TrimSuffix(prefix[offset:lineEnd], "\r")
|
|
44
|
+
if alias, ok := unusedTypiaImportAlias(line); ok &&
|
|
45
|
+
!cleanupIdentifierStillReferenced(text, alias, offset, nextOffset) {
|
|
46
|
+
removals = append(removals, removal{start: offset, end: nextOffset})
|
|
47
|
+
}
|
|
48
|
+
offset = nextOffset
|
|
49
|
+
}
|
|
50
|
+
for i := len(removals) - 1; i >= 0; i-- {
|
|
51
|
+
r := removals[i]
|
|
52
|
+
text = text[:r.start] + text[r.end:]
|
|
53
|
+
}
|
|
54
|
+
return text
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func unusedTypiaImportAlias(line string) (string, bool) {
|
|
58
|
+
if strings.HasPrefix(line, "const ") {
|
|
59
|
+
index := strings.Index(line, " = ")
|
|
60
|
+
if index < 0 {
|
|
61
|
+
return "", false
|
|
62
|
+
}
|
|
63
|
+
alias := strings.TrimSpace(strings.TrimPrefix(line[:index], "const "))
|
|
64
|
+
if isTypiaImportAlias(alias) == false {
|
|
65
|
+
return "", false
|
|
66
|
+
}
|
|
67
|
+
value := strings.TrimSpace(line[index+3:])
|
|
68
|
+
return alias, value == `__importDefault(require("typia"));` ||
|
|
69
|
+
value == `__importDefault(require('typia'));` ||
|
|
70
|
+
value == `require("typia");` ||
|
|
71
|
+
value == `require('typia');`
|
|
72
|
+
}
|
|
73
|
+
if strings.HasPrefix(line, "import ") && strings.HasSuffix(line, ` from "typia";`) {
|
|
74
|
+
alias := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "import "), ` from "typia";`))
|
|
75
|
+
return alias, isTypiaImportAlias(alias)
|
|
76
|
+
}
|
|
77
|
+
if strings.HasPrefix(line, "import ") && strings.HasSuffix(line, ` from 'typia';`) {
|
|
78
|
+
alias := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "import "), ` from 'typia';`))
|
|
79
|
+
return alias, isTypiaImportAlias(alias)
|
|
80
|
+
}
|
|
81
|
+
return "", false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func isTypiaImportAlias(alias string) bool {
|
|
85
|
+
if alias == "typia" {
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
if !strings.HasPrefix(alias, "typia_") {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
for _, ch := range alias[len("typia_"):] {
|
|
92
|
+
if ch < '0' || ch > '9' {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return len(alias) > len("typia_")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func cleanupIdentifierStillReferenced(text, alias string, lineStart, lineEnd int) bool {
|
|
100
|
+
return cleanupIdentifierAppears(text[:lineStart], alias) ||
|
|
101
|
+
cleanupIdentifierAppears(text[lineEnd:], alias)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func cleanupIdentifierAppears(text string, alias string) bool {
|
|
105
|
+
offset := 0
|
|
106
|
+
for {
|
|
107
|
+
index := strings.Index(text[offset:], alias)
|
|
108
|
+
if index < 0 {
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
start := offset + index
|
|
112
|
+
end := start + len(alias)
|
|
113
|
+
if (start == 0 || !isCleanupRuntimeAliasIdentifierByte(text[start-1])) &&
|
|
114
|
+
(end == len(text) || !isCleanupRuntimeAliasIdentifierByte(text[end])) {
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
offset = end
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func injectCleanupRuntimeImports(text string) string {
|
|
122
|
+
aliases := collectCleanupRuntimeAliases(text)
|
|
123
|
+
return injectCleanupRuntimeImportsWithAliases(text, aliases)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func injectCleanupRuntimeImportsWithAliases(text string, aliases []string) string {
|
|
127
|
+
if len(aliases) == 0 {
|
|
128
|
+
return text
|
|
129
|
+
}
|
|
130
|
+
esModule := isCleanupESModuleOutput(text)
|
|
131
|
+
existing := collectExistingCleanupRuntimeImports(text)
|
|
132
|
+
imports := make([]string, 0, len(aliases))
|
|
133
|
+
for _, alias := range aliases {
|
|
134
|
+
module := cleanupRuntimeModuleOf(alias)
|
|
135
|
+
if existing[cleanupRuntimeImportKey(alias, module)] {
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
name := cleanupRuntimeNameOf(alias)
|
|
139
|
+
if esModule {
|
|
140
|
+
imports = append(imports, fmt.Sprintf(`import { %s as %s } from %q;`, name, alias, module))
|
|
141
|
+
} else {
|
|
142
|
+
imports = append(imports, fmt.Sprintf(`const { %s: %s } = require(%q);`, name, alias, module))
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if len(imports) == 0 {
|
|
146
|
+
return text
|
|
147
|
+
}
|
|
148
|
+
index := cleanupRuntimeImportInsertionIndex(text, esModule)
|
|
149
|
+
return text[:index] + strings.Join(imports, "\n") + "\n" + text[index:]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func collectCleanupRuntimeAliases(text string) []string {
|
|
153
|
+
seen := map[string]bool{}
|
|
154
|
+
offset := 0
|
|
155
|
+
for {
|
|
156
|
+
index := strings.Index(text[offset:], cleanupRuntimeAliasPrefix)
|
|
157
|
+
if index < 0 {
|
|
158
|
+
break
|
|
159
|
+
}
|
|
160
|
+
start := offset + index
|
|
161
|
+
end := start + len(cleanupRuntimeAliasPrefix)
|
|
162
|
+
if start != 0 && isCleanupRuntimeAliasIdentifierByte(text[start-1]) {
|
|
163
|
+
offset = end
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
for end < len(text) && isCleanupRuntimeAliasNameByte(text[end]) {
|
|
167
|
+
end++
|
|
168
|
+
}
|
|
169
|
+
if end != start+len(cleanupRuntimeAliasPrefix) &&
|
|
170
|
+
(end == len(text) || !isCleanupRuntimeAliasIdentifierByte(text[end])) {
|
|
171
|
+
seen[text[start:end]] = true
|
|
172
|
+
}
|
|
173
|
+
offset = end
|
|
174
|
+
}
|
|
175
|
+
return sortCleanupRuntimeAliases(seen)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
func sortCleanupRuntimeAliases(seen map[string]bool) []string {
|
|
179
|
+
aliases := make([]string, 0, len(seen))
|
|
180
|
+
for alias := range seen {
|
|
181
|
+
aliases = append(aliases, alias)
|
|
182
|
+
}
|
|
183
|
+
sort.SliceStable(aliases, func(i, j int) bool {
|
|
184
|
+
left, right := cleanupRuntimeAliasRank(aliases[i]), cleanupRuntimeAliasRank(aliases[j])
|
|
185
|
+
if left != right {
|
|
186
|
+
return left < right
|
|
187
|
+
}
|
|
188
|
+
return aliases[i] < aliases[j]
|
|
189
|
+
})
|
|
190
|
+
return aliases
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const cleanupRuntimeAliasPrefix = "__typia_transform_"
|
|
194
|
+
|
|
195
|
+
func isCleanupRuntimeAliasNameByte(value byte) bool {
|
|
196
|
+
return (value >= 'A' && value <= 'Z') ||
|
|
197
|
+
(value >= 'a' && value <= 'z') ||
|
|
198
|
+
(value >= '0' && value <= '9') ||
|
|
199
|
+
value == '_'
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func isCleanupRuntimeAliasIdentifierByte(value byte) bool {
|
|
203
|
+
return isCleanupRuntimeAliasNameByte(value) || value == '$'
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
func cleanupRuntimeAliasRank(alias string) int {
|
|
207
|
+
name := strings.TrimPrefix(alias, cleanupRuntimeAliasPrefix)
|
|
208
|
+
switch {
|
|
209
|
+
case strings.HasPrefix(name, "_is"):
|
|
210
|
+
return 100
|
|
211
|
+
case strings.HasPrefix(name, "_assert"):
|
|
212
|
+
return 150
|
|
213
|
+
case strings.HasPrefix(name, "_randomFormat"):
|
|
214
|
+
return 200
|
|
215
|
+
case name == "_randomString":
|
|
216
|
+
return 210
|
|
217
|
+
case name == "_randomInteger":
|
|
218
|
+
return 220
|
|
219
|
+
case name == "_randomNumber":
|
|
220
|
+
return 221
|
|
221
|
+
case strings.HasPrefix(name, "_random"):
|
|
222
|
+
return 230
|
|
223
|
+
case name == "_validateReport":
|
|
224
|
+
return 800
|
|
225
|
+
case name == "_createStandardSchema":
|
|
226
|
+
return 900
|
|
227
|
+
}
|
|
228
|
+
return 500
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
func cleanupRuntimeModuleOf(alias string) string {
|
|
232
|
+
return "typia/lib/internal/" + cleanupRuntimeNameOf(alias)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
func cleanupRuntimeNameOf(alias string) string {
|
|
236
|
+
name := strings.TrimPrefix(alias, cleanupRuntimeAliasPrefix)
|
|
237
|
+
if !strings.HasPrefix(name, "_") {
|
|
238
|
+
name = "_" + name
|
|
239
|
+
}
|
|
240
|
+
return name
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
func collectExistingCleanupRuntimeImports(text string) map[string]bool {
|
|
244
|
+
output := map[string]bool{}
|
|
245
|
+
forEachCleanupImportLine(cleanupImportScanPrefix(text), func(line string) {
|
|
246
|
+
if !strings.Contains(line, cleanupRuntimeAliasPrefix) ||
|
|
247
|
+
!strings.Contains(line, "typia/lib/internal/") {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
alias, ok := cleanupRuntimeImportAlias(line)
|
|
251
|
+
if !ok {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
module, ok := cleanupRuntimeImportModule(line)
|
|
255
|
+
if !ok {
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
output[cleanupRuntimeImportKey(alias, module)] = true
|
|
259
|
+
})
|
|
260
|
+
return output
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
func cleanupImportScanPrefix(text string) string {
|
|
264
|
+
const limit = 1 << 20
|
|
265
|
+
if len(text) <= limit {
|
|
266
|
+
return text
|
|
267
|
+
}
|
|
268
|
+
return text[:limit]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
func cleanupRuntimeImportKey(alias string, module string) string {
|
|
272
|
+
return alias + "\x00" + module
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
func forEachCleanupImportLine(text string, iterate func(string)) {
|
|
276
|
+
for len(text) > 0 {
|
|
277
|
+
next := strings.IndexByte(text, '\n')
|
|
278
|
+
line := text
|
|
279
|
+
if next >= 0 {
|
|
280
|
+
line = text[:next]
|
|
281
|
+
text = text[next+1:]
|
|
282
|
+
} else {
|
|
283
|
+
text = ""
|
|
284
|
+
}
|
|
285
|
+
iterate(strings.TrimSuffix(line, "\r"))
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func cleanupRuntimeImportAlias(line string) (string, bool) {
|
|
290
|
+
switch {
|
|
291
|
+
case strings.HasPrefix(line, "const { "):
|
|
292
|
+
colon := strings.IndexByte(line, ':')
|
|
293
|
+
if colon < 0 {
|
|
294
|
+
return "", false
|
|
295
|
+
}
|
|
296
|
+
end := strings.IndexByte(line[colon+1:], '}')
|
|
297
|
+
if end < 0 {
|
|
298
|
+
return "", false
|
|
299
|
+
}
|
|
300
|
+
return cleanupFirstIdentifier(strings.TrimSpace(line[colon+1 : colon+1+end]))
|
|
301
|
+
case strings.HasPrefix(line, "import { "):
|
|
302
|
+
index := strings.LastIndex(line, " as ")
|
|
303
|
+
if index < 0 {
|
|
304
|
+
return "", false
|
|
305
|
+
}
|
|
306
|
+
end := strings.IndexByte(line[index+4:], '}')
|
|
307
|
+
if end < 0 {
|
|
308
|
+
return "", false
|
|
309
|
+
}
|
|
310
|
+
return cleanupFirstIdentifier(strings.TrimSpace(line[index+4 : index+4+end]))
|
|
311
|
+
case strings.HasPrefix(line, "const "):
|
|
312
|
+
rest := strings.TrimPrefix(line, "const ")
|
|
313
|
+
index := strings.Index(rest, " = require(")
|
|
314
|
+
if index < 0 {
|
|
315
|
+
return "", false
|
|
316
|
+
}
|
|
317
|
+
return cleanupFirstIdentifier(rest[:index])
|
|
318
|
+
case strings.HasPrefix(line, "import * as "):
|
|
319
|
+
rest := strings.TrimPrefix(line, "import * as ")
|
|
320
|
+
index := strings.Index(rest, " from ")
|
|
321
|
+
if index < 0 {
|
|
322
|
+
return "", false
|
|
323
|
+
}
|
|
324
|
+
return cleanupFirstIdentifier(rest[:index])
|
|
325
|
+
default:
|
|
326
|
+
return "", false
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
func cleanupFirstIdentifier(text string) (string, bool) {
|
|
331
|
+
fields := strings.Fields(text)
|
|
332
|
+
if len(fields) == 0 {
|
|
333
|
+
return "", false
|
|
334
|
+
}
|
|
335
|
+
name := strings.Trim(fields[0], " \t{}")
|
|
336
|
+
if !strings.HasPrefix(name, cleanupRuntimeAliasPrefix) {
|
|
337
|
+
return "", false
|
|
338
|
+
}
|
|
339
|
+
return name, true
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
func cleanupRuntimeImportModule(line string) (string, bool) {
|
|
343
|
+
for _, marker := range []string{`require("`, `require('`, ` from "`, ` from '`} {
|
|
344
|
+
index := strings.LastIndex(line, marker)
|
|
345
|
+
if index < 0 {
|
|
346
|
+
continue
|
|
347
|
+
}
|
|
348
|
+
start := index + len(marker)
|
|
349
|
+
quote := marker[len(marker)-1]
|
|
350
|
+
end := strings.IndexByte(line[start:], quote)
|
|
351
|
+
if end < 0 {
|
|
352
|
+
return "", false
|
|
353
|
+
}
|
|
354
|
+
module := line[start : start+end]
|
|
355
|
+
if !strings.HasPrefix(module, "typia/lib/internal/") {
|
|
356
|
+
return "", false
|
|
357
|
+
}
|
|
358
|
+
return module, true
|
|
359
|
+
}
|
|
360
|
+
return "", false
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
func isCleanupESModuleOutput(text string) bool {
|
|
364
|
+
output := false
|
|
365
|
+
forEachCleanupImportLine(cleanupImportScanPrefix(text), func(line string) {
|
|
366
|
+
if strings.HasPrefix(line, "import ") ||
|
|
367
|
+
strings.HasPrefix(line, "import{") ||
|
|
368
|
+
strings.HasPrefix(line, "import*") ||
|
|
369
|
+
strings.HasPrefix(line, "export ") {
|
|
370
|
+
output = true
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
return output
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
func cleanupRuntimeImportInsertionIndex(text string, esModule bool) int {
|
|
377
|
+
index := 0
|
|
378
|
+
if strings.HasPrefix(text, "#!") {
|
|
379
|
+
if next := strings.IndexByte(text, '\n'); next >= 0 {
|
|
380
|
+
index = next + 1
|
|
381
|
+
} else {
|
|
382
|
+
return len(text)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if esModule {
|
|
386
|
+
return index
|
|
387
|
+
}
|
|
388
|
+
for {
|
|
389
|
+
next := consumeCleanupRuntimeImportPrefix(text[index:])
|
|
390
|
+
if next == 0 {
|
|
391
|
+
return index
|
|
392
|
+
}
|
|
393
|
+
index += next
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
func consumeCleanupRuntimeImportPrefix(text string) int {
|
|
398
|
+
for _, prefix := range []string{
|
|
399
|
+
"\"use strict\";\n",
|
|
400
|
+
"'use strict';\n",
|
|
401
|
+
"/* @ttsc-rewritten */\n",
|
|
402
|
+
} {
|
|
403
|
+
if strings.HasPrefix(text, prefix) {
|
|
404
|
+
return len(prefix)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return 0
|
|
408
|
+
}
|
|
@@ -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
|
+
}
|