@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.
Files changed (178) hide show
  1. package/MIGRATION.md +169 -0
  2. package/lib/adaptors/WebSocketAdaptor.js +7 -3
  3. package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
  4. package/lib/decorators/DynamicModule.js.map +1 -1
  5. package/lib/decorators/EncryptedBody.js.map +1 -1
  6. package/lib/decorators/EncryptedController.js.map +1 -1
  7. package/lib/decorators/EncryptedModule.js.map +1 -1
  8. package/lib/decorators/EncryptedRoute.js.map +1 -1
  9. package/lib/decorators/HumanRoute.js.map +1 -1
  10. package/lib/decorators/NoTransformConfigurationError.js +5 -2
  11. package/lib/decorators/NoTransformConfigurationError.js.map +1 -1
  12. package/lib/decorators/PlainBody.js.map +1 -1
  13. package/lib/decorators/SwaggerCustomizer.js.map +1 -1
  14. package/lib/decorators/SwaggerExample.js.map +1 -1
  15. package/lib/decorators/TypedBody.js.map +1 -1
  16. package/lib/decorators/TypedException.js.map +1 -1
  17. package/lib/decorators/TypedFormData.js.map +1 -1
  18. package/lib/decorators/TypedHeaders.js.map +1 -1
  19. package/lib/decorators/TypedParam.js +5 -2
  20. package/lib/decorators/TypedParam.js.map +1 -1
  21. package/lib/decorators/TypedQuery.js.map +1 -1
  22. package/lib/decorators/TypedRoute.js.map +1 -1
  23. package/lib/decorators/WebSocketRoute.js.map +1 -1
  24. package/lib/decorators/doNotThrowTransformError.js.map +1 -1
  25. package/lib/decorators/internal/get_path_and_querify.js +5 -2
  26. package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
  27. package/lib/decorators/internal/get_path_and_stringify.js +5 -2
  28. package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
  29. package/lib/decorators/internal/get_text_body.js.map +1 -1
  30. package/lib/decorators/internal/headers_to_object.js.map +1 -1
  31. package/lib/decorators/internal/is_request_body_undefined.js.map +1 -1
  32. package/lib/decorators/internal/load_controller.js +48 -16
  33. package/lib/decorators/internal/load_controller.js.map +1 -1
  34. package/lib/decorators/internal/route_error.js +3 -3
  35. package/lib/decorators/internal/route_error.js.map +1 -1
  36. package/lib/decorators/internal/validate_request_body.js +5 -2
  37. package/lib/decorators/internal/validate_request_body.js.map +1 -1
  38. package/lib/decorators/internal/validate_request_form_data.js +5 -2
  39. package/lib/decorators/internal/validate_request_form_data.js.map +1 -1
  40. package/lib/decorators/internal/validate_request_headers.js +5 -2
  41. package/lib/decorators/internal/validate_request_headers.js.map +1 -1
  42. package/lib/decorators/internal/validate_request_query.js +30 -4
  43. package/lib/decorators/internal/validate_request_query.js.map +1 -1
  44. package/lib/index.js.map +1 -1
  45. package/lib/transform.d.ts +10 -5
  46. package/lib/transform.js +64 -28
  47. package/lib/transform.js.map +1 -1
  48. package/lib/utils/ArrayUtil.js.map +1 -1
  49. package/lib/utils/ExceptionManager.js.map +1 -1
  50. package/lib/utils/Singleton.js.map +1 -1
  51. package/lib/utils/SourceFinder.js.map +1 -1
  52. package/lib/utils/VersioningStrategy.js.map +1 -1
  53. package/native/cmd/ttsc-nestia/main.go +11 -0
  54. package/native/go.mod +32 -0
  55. package/native/go.sum +54 -0
  56. package/native/plugin/plan.go +102 -0
  57. package/native/transform/ast.go +32 -0
  58. package/native/transform/build.go +413 -0
  59. package/native/transform/cleanup.go +408 -0
  60. package/native/transform/cleanup_test.go +76 -0
  61. package/native/transform/commonjs_import_alias_test.go +49 -0
  62. package/native/transform/core_dispatch_test.go +127 -0
  63. package/native/transform/core_querify.go +227 -0
  64. package/native/transform/core_transform.go +1713 -0
  65. package/native/transform/core_websocket.go +115 -0
  66. package/native/transform/exports.go +13 -0
  67. package/native/transform/path_rewrite.go +285 -0
  68. package/native/transform/path_rewrite_test.go +243 -0
  69. package/native/transform/printer.go +244 -0
  70. package/native/transform/rewrite.go +662 -0
  71. package/native/transform/rewrite_test.go +118 -0
  72. package/native/transform/rewrite_unique_base_test.go +48 -0
  73. package/native/transform/run.go +72 -0
  74. package/native/transform/transform.go +376 -0
  75. package/native/transform/typia_fast.go +326 -0
  76. package/native/transform/typia_replacement.go +24 -0
  77. package/native/transform.cjs +43 -0
  78. package/package.json +28 -22
  79. package/src/decorators/NoTransformConfigurationError.ts +5 -2
  80. package/src/decorators/internal/load_controller.ts +50 -19
  81. package/src/decorators/internal/validate_request_query.ts +21 -2
  82. package/src/transform.ts +101 -35
  83. package/lib/decorators/internal/NoTransformConfigureError.d.ts +0 -1
  84. package/lib/decorators/internal/NoTransformConfigureError.js +0 -7
  85. package/lib/decorators/internal/NoTransformConfigureError.js.map +0 -1
  86. package/lib/options/INestiaTransformOptions.d.ts +0 -13
  87. package/lib/options/INestiaTransformOptions.js +0 -3
  88. package/lib/options/INestiaTransformOptions.js.map +0 -1
  89. package/lib/options/INestiaTransformProject.d.ts +0 -5
  90. package/lib/options/INestiaTransformProject.js +0 -3
  91. package/lib/options/INestiaTransformProject.js.map +0 -1
  92. package/lib/programmers/PlainBodyProgrammer.d.ts +0 -9
  93. package/lib/programmers/PlainBodyProgrammer.js +0 -58
  94. package/lib/programmers/PlainBodyProgrammer.js.map +0 -1
  95. package/lib/programmers/TypedBodyProgrammer.d.ts +0 -9
  96. package/lib/programmers/TypedBodyProgrammer.js +0 -94
  97. package/lib/programmers/TypedBodyProgrammer.js.map +0 -1
  98. package/lib/programmers/TypedFormDataBodyProgrammer.d.ts +0 -9
  99. package/lib/programmers/TypedFormDataBodyProgrammer.js +0 -65
  100. package/lib/programmers/TypedFormDataBodyProgrammer.js.map +0 -1
  101. package/lib/programmers/TypedHeadersProgrammer.d.ts +0 -9
  102. package/lib/programmers/TypedHeadersProgrammer.js +0 -38
  103. package/lib/programmers/TypedHeadersProgrammer.js.map +0 -1
  104. package/lib/programmers/TypedParamProgrammer.d.ts +0 -10
  105. package/lib/programmers/TypedParamProgrammer.js +0 -32
  106. package/lib/programmers/TypedParamProgrammer.js.map +0 -1
  107. package/lib/programmers/TypedQueryBodyProgrammer.d.ts +0 -9
  108. package/lib/programmers/TypedQueryBodyProgrammer.js +0 -74
  109. package/lib/programmers/TypedQueryBodyProgrammer.js.map +0 -1
  110. package/lib/programmers/TypedQueryProgrammer.d.ts +0 -9
  111. package/lib/programmers/TypedQueryProgrammer.js +0 -75
  112. package/lib/programmers/TypedQueryProgrammer.js.map +0 -1
  113. package/lib/programmers/TypedQueryRouteProgrammer.d.ts +0 -9
  114. package/lib/programmers/TypedQueryRouteProgrammer.js +0 -75
  115. package/lib/programmers/TypedQueryRouteProgrammer.js.map +0 -1
  116. package/lib/programmers/TypedRouteProgrammer.d.ts +0 -9
  117. package/lib/programmers/TypedRouteProgrammer.js +0 -79
  118. package/lib/programmers/TypedRouteProgrammer.js.map +0 -1
  119. package/lib/programmers/http/HttpAssertQuerifyProgrammer.d.ts +0 -9
  120. package/lib/programmers/http/HttpAssertQuerifyProgrammer.js +0 -39
  121. package/lib/programmers/http/HttpAssertQuerifyProgrammer.js.map +0 -1
  122. package/lib/programmers/http/HttpIsQuerifyProgrammer.d.ts +0 -9
  123. package/lib/programmers/http/HttpIsQuerifyProgrammer.js +0 -36
  124. package/lib/programmers/http/HttpIsQuerifyProgrammer.js.map +0 -1
  125. package/lib/programmers/http/HttpQuerifyProgrammer.d.ts +0 -9
  126. package/lib/programmers/http/HttpQuerifyProgrammer.js +0 -50
  127. package/lib/programmers/http/HttpQuerifyProgrammer.js.map +0 -1
  128. package/lib/programmers/http/HttpValidateQuerifyProgrammer.d.ts +0 -9
  129. package/lib/programmers/http/HttpValidateQuerifyProgrammer.js +0 -40
  130. package/lib/programmers/http/HttpValidateQuerifyProgrammer.js.map +0 -1
  131. package/lib/programmers/internal/CoreMetadataUtil.d.ts +0 -5
  132. package/lib/programmers/internal/CoreMetadataUtil.js +0 -19
  133. package/lib/programmers/internal/CoreMetadataUtil.js.map +0 -1
  134. package/lib/transformers/FileTransformer.d.ts +0 -5
  135. package/lib/transformers/FileTransformer.js +0 -80
  136. package/lib/transformers/FileTransformer.js.map +0 -1
  137. package/lib/transformers/MethodTransformer.d.ts +0 -8
  138. package/lib/transformers/MethodTransformer.js +0 -58
  139. package/lib/transformers/MethodTransformer.js.map +0 -1
  140. package/lib/transformers/NodeTransformer.d.ts +0 -8
  141. package/lib/transformers/NodeTransformer.js +0 -24
  142. package/lib/transformers/NodeTransformer.js.map +0 -1
  143. package/lib/transformers/ParameterDecoratorTransformer.d.ts +0 -9
  144. package/lib/transformers/ParameterDecoratorTransformer.js +0 -104
  145. package/lib/transformers/ParameterDecoratorTransformer.js.map +0 -1
  146. package/lib/transformers/ParameterTransformer.d.ts +0 -8
  147. package/lib/transformers/ParameterTransformer.js +0 -37
  148. package/lib/transformers/ParameterTransformer.js.map +0 -1
  149. package/lib/transformers/TypedRouteTransformer.d.ts +0 -9
  150. package/lib/transformers/TypedRouteTransformer.js +0 -68
  151. package/lib/transformers/TypedRouteTransformer.js.map +0 -1
  152. package/lib/transformers/WebSocketRouteTransformer.d.ts +0 -9
  153. package/lib/transformers/WebSocketRouteTransformer.js +0 -72
  154. package/lib/transformers/WebSocketRouteTransformer.js.map +0 -1
  155. package/src/decorators/internal/NoTransformConfigureError.ts +0 -2
  156. package/src/options/INestiaTransformOptions.ts +0 -34
  157. package/src/options/INestiaTransformProject.ts +0 -10
  158. package/src/programmers/PlainBodyProgrammer.ts +0 -72
  159. package/src/programmers/TypedBodyProgrammer.ts +0 -148
  160. package/src/programmers/TypedFormDataBodyProgrammer.ts +0 -118
  161. package/src/programmers/TypedHeadersProgrammer.ts +0 -65
  162. package/src/programmers/TypedParamProgrammer.ts +0 -33
  163. package/src/programmers/TypedQueryBodyProgrammer.ts +0 -113
  164. package/src/programmers/TypedQueryProgrammer.ts +0 -115
  165. package/src/programmers/TypedQueryRouteProgrammer.ts +0 -107
  166. package/src/programmers/TypedRouteProgrammer.ts +0 -103
  167. package/src/programmers/http/HttpAssertQuerifyProgrammer.ts +0 -74
  168. package/src/programmers/http/HttpIsQuerifyProgrammer.ts +0 -77
  169. package/src/programmers/http/HttpQuerifyProgrammer.ts +0 -110
  170. package/src/programmers/http/HttpValidateQuerifyProgrammer.ts +0 -78
  171. package/src/programmers/internal/CoreMetadataUtil.ts +0 -21
  172. package/src/transformers/FileTransformer.ts +0 -109
  173. package/src/transformers/MethodTransformer.ts +0 -103
  174. package/src/transformers/NodeTransformer.ts +0 -23
  175. package/src/transformers/ParameterDecoratorTransformer.ts +0 -143
  176. package/src/transformers/ParameterTransformer.ts +0 -57
  177. package/src/transformers/TypedRouteTransformer.ts +0 -85
  178. package/src/transformers/WebSocketRouteTransformer.ts +0 -120
@@ -0,0 +1,662 @@
1
+ package transform
2
+
3
+ import (
4
+ "fmt"
5
+ "path/filepath"
6
+ "sort"
7
+ "strings"
8
+ "unicode"
9
+ )
10
+
11
+ type nativeRewrite struct {
12
+ FilePath string
13
+ RootName string
14
+ Namespaces []string
15
+ Method string
16
+ Replacement string
17
+ ConsumeParens bool
18
+ AppendArguments []string
19
+ TargetExpressionCandidates []string
20
+ SourceStart int
21
+ ExpectedArgumentCount *int
22
+ ExpectedArgumentsText string
23
+ }
24
+
25
+ type nativeRewriteSet struct {
26
+ byPath map[string][]nativeRewrite
27
+ aliasesByPath map[string]map[string]bool
28
+ sortedAliasesByPath map[string][]string
29
+ }
30
+
31
+ func newNativeRewriteSet() *nativeRewriteSet {
32
+ return &nativeRewriteSet{
33
+ byPath: map[string][]nativeRewrite{},
34
+ aliasesByPath: map[string]map[string]bool{},
35
+ sortedAliasesByPath: map[string][]string{},
36
+ }
37
+ }
38
+
39
+ func (rs *nativeRewriteSet) Add(r nativeRewrite) {
40
+ if r.FilePath == "" {
41
+ return
42
+ }
43
+ path := filepath.ToSlash(r.FilePath)
44
+ rs.byPath[path] = append(rs.byPath[path], r)
45
+ rs.addRuntimeAliases(path, r.Replacement)
46
+ for _, argument := range r.AppendArguments {
47
+ rs.addRuntimeAliases(path, argument)
48
+ }
49
+ }
50
+
51
+ func (rs *nativeRewriteSet) addRuntimeAliases(path string, text string) {
52
+ for _, alias := range collectCleanupRuntimeAliases(text) {
53
+ if rs.aliasesByPath[path] == nil {
54
+ rs.aliasesByPath[path] = map[string]bool{}
55
+ }
56
+ rs.aliasesByPath[path][alias] = true
57
+ delete(rs.sortedAliasesByPath, path)
58
+ }
59
+ }
60
+
61
+ func (rs *nativeRewriteSet) Len() int {
62
+ if rs == nil {
63
+ return 0
64
+ }
65
+ n := 0
66
+ for _, rewrites := range rs.byPath {
67
+ n += len(rewrites)
68
+ }
69
+ return n
70
+ }
71
+
72
+ func (rs *nativeRewriteSet) Apply(outputName string, text string, cursors map[string]int) (string, error) {
73
+ if rs == nil || len(rs.byPath) == 0 {
74
+ return text, nil
75
+ }
76
+ if strings.Contains(text, "/* @ttsc-rewritten */") {
77
+ return text, nil
78
+ }
79
+ srcPath, ok := rs.findSourceForOutput(outputName)
80
+ if !ok || len(rs.byPath[srcPath]) == 0 {
81
+ return text, nil
82
+ }
83
+ rewrites := rs.byPath[srcPath]
84
+ sort.SliceStable(rewrites, func(i, j int) bool {
85
+ left, leftOK := nativeRewriteFirstIndex(text, rewrites[i])
86
+ right, rightOK := nativeRewriteFirstIndex(text, rewrites[j])
87
+ if leftOK && rightOK && left != right {
88
+ return left < right
89
+ }
90
+ if leftOK != rightOK {
91
+ return leftOK
92
+ }
93
+ return rewrites[i].SourceStart < rewrites[j].SourceStart
94
+ })
95
+ pos := cursors[srcPath]
96
+ out := text
97
+ for pos < len(rewrites) {
98
+ rewrite := rewrites[pos]
99
+ replaced, ok, err := spliceNativeCall(out, rewrite)
100
+ if err != nil {
101
+ return "", err
102
+ }
103
+ if !ok {
104
+ preview := out
105
+ if len(preview) > 400 {
106
+ preview = preview[:400] + "..."
107
+ }
108
+ return "", fmt.Errorf(
109
+ "native rewrite: could not locate %s.%s(...) call in %s (tried roots %v; preview: %q)",
110
+ joinNativeRootAndNamespaces(rewrite),
111
+ rewrite.Method,
112
+ outputName,
113
+ candidateNativeRoots(rewrite.RootName),
114
+ preview,
115
+ )
116
+ }
117
+ out = replaced
118
+ pos++
119
+ }
120
+ cursors[srcPath] = pos
121
+ if out != text {
122
+ out = insertNativeRewriteSentinel(out)
123
+ }
124
+ return out, nil
125
+ }
126
+
127
+ func (rs *nativeRewriteSet) RuntimeAliasesForOutput(outputName string) []string {
128
+ if rs == nil || len(rs.aliasesByPath) == 0 || isJavaScriptOutput(outputName) == false {
129
+ return nil
130
+ }
131
+ srcPath, ok := rs.findSourceForOutput(outputName)
132
+ if !ok {
133
+ return nil
134
+ }
135
+ if aliases, ok := rs.sortedAliasesByPath[srcPath]; ok {
136
+ return aliases
137
+ }
138
+ seen := rs.aliasesByPath[srcPath]
139
+ if len(seen) == 0 {
140
+ rs.sortedAliasesByPath[srcPath] = []string{}
141
+ return rs.sortedAliasesByPath[srcPath]
142
+ }
143
+ aliases := sortCleanupRuntimeAliases(seen)
144
+ rs.sortedAliasesByPath[srcPath] = aliases
145
+ return aliases
146
+ }
147
+
148
+ func nativeRewriteFirstIndex(text string, rewrite nativeRewrite) (int, bool) {
149
+ best := -1
150
+ for _, target := range candidateNativeTargets(rewrite) {
151
+ hit, ok := indexNativeFlexibleCall(text, target, rewrite, 0)
152
+ if ok == false {
153
+ continue
154
+ }
155
+ if best < 0 || hit.start < best {
156
+ best = hit.start
157
+ }
158
+ }
159
+ return best, best >= 0
160
+ }
161
+
162
+ func (rs *nativeRewriteSet) findSourceForOutput(outputName string) (string, bool) {
163
+ outSlash := strings.TrimSuffix(filepath.ToSlash(outputName), filepath.Ext(outputName))
164
+ for path := range rs.byPath {
165
+ srcStem := strings.TrimSuffix(filepath.ToSlash(path), filepath.Ext(path))
166
+ if OutputMatchesSourceStem(outSlash, srcStem) {
167
+ return path, true
168
+ }
169
+ }
170
+ return rs.findSourceByUniqueBase(outSlash)
171
+ }
172
+
173
+ func (rs *nativeRewriteSet) findSourceByUniqueBase(outputStem string) (string, bool) {
174
+ // Only fall back to basename matching when the output looks like an actual
175
+ // build product (sits inside lib/dist/bin/build). If the output lives in
176
+ // the source tree (e.g. ttsc's virtual filesystem mirroring src/), basename
177
+ // matching crosses unrelated files — `src/index.ts` would silently absorb
178
+ // the rewrite intended for every `src/.../index.ts` sibling.
179
+ if outputHasBuildMarker(outputStem) == false {
180
+ return "", false
181
+ }
182
+ base := pathBase(outputStem)
183
+ if base == "" {
184
+ return "", false
185
+ }
186
+ matched := ""
187
+ count := 0
188
+ for path := range rs.byPath {
189
+ srcStem := strings.TrimSuffix(filepath.ToSlash(path), filepath.Ext(path))
190
+ if pathBase(srcStem) != base {
191
+ continue
192
+ }
193
+ matched = path
194
+ count++
195
+ if count > 1 {
196
+ return "", false
197
+ }
198
+ }
199
+ return matched, count == 1
200
+ }
201
+
202
+ func outputHasBuildMarker(stem string) bool {
203
+ for _, marker := range []string{"lib", "dist", "bin", "build"} {
204
+ if _, ok := suffixAfterPathMarker(stem, []string{marker}, false); ok {
205
+ return true
206
+ }
207
+ }
208
+ return false
209
+ }
210
+
211
+ func OutputMatchesSourceStem(outputStem string, sourceStem string) bool {
212
+ if outputStem == sourceStem {
213
+ return true
214
+ }
215
+ for _, sourceRel := range sourceOutputCandidates(sourceStem) {
216
+ for _, outputRel := range outputSourceCandidates(outputStem) {
217
+ if sourceRel == outputRel {
218
+ return true
219
+ }
220
+ }
221
+ // Suffix-only matching is a fallback for paths that don't share a
222
+ // recognized build marker (e.g. ttsc's virtual filesystem mirroring
223
+ // the source tree). Require the relative path to span at least two
224
+ // segments so a top-level source like `src/index.ts` does not match
225
+ // every output file that happens to end in `/index.js`.
226
+ if strings.Contains(sourceRel, "/") && strings.HasSuffix(outputStem, "/"+sourceRel) {
227
+ return true
228
+ }
229
+ }
230
+ return false
231
+ }
232
+
233
+ func pathBase(stem string) string {
234
+ stem = strings.TrimSuffix(filepath.ToSlash(stem), "/")
235
+ if stem == "" {
236
+ return ""
237
+ }
238
+ if idx := strings.LastIndexByte(stem, '/'); idx >= 0 {
239
+ return stem[idx+1:]
240
+ }
241
+ return stem
242
+ }
243
+
244
+ func isJavaScriptOutput(fileName string) bool {
245
+ switch strings.ToLower(filepath.Ext(fileName)) {
246
+ case ".js", ".mjs", ".cjs":
247
+ return true
248
+ default:
249
+ return false
250
+ }
251
+ }
252
+
253
+ func sourceOutputCandidates(stem string) []string {
254
+ candidates := []string{}
255
+ if rel, ok := suffixAfterPathMarker(stem, []string{"src", "api"}, false); ok {
256
+ candidates = append(candidates, rel)
257
+ }
258
+ if rel, ok := suffixAfterPathMarker(stem, []string{"src"}, false); ok {
259
+ candidates = append(candidates, rel)
260
+ }
261
+ if rel, ok := suffixAfterPathMarker(stem, []string{"test"}, true); ok {
262
+ candidates = append(candidates, rel)
263
+ }
264
+ return candidates
265
+ }
266
+
267
+ func outputSourceCandidates(stem string) []string {
268
+ candidates := []string{}
269
+ // Mirror sourceOutputCandidates so paths whose source and output trees
270
+ // share the same `src/` (or `test/`) root — e.g. ttsc's virtual filesystem
271
+ // emits next to the source — can intersect on the same relative tail.
272
+ // We intentionally do NOT include the {"src","api"} marker here: that
273
+ // marker exists on the source side to let a source under `src/api/`
274
+ // pretend its rel is the leaf only (so it matches an output that lacks
275
+ // the `api/` segment); mirroring it on the output side would let two
276
+ // unrelated files (e.g. `src/index.ts` and `src/api/index.ts`) collide
277
+ // on the rel "index".
278
+ for _, marker := range [][]string{{"lib"}, {"bin"}, {"dist"}, {"build"}, {"src"}} {
279
+ if rel, ok := suffixAfterPathMarker(stem, marker, false); ok {
280
+ candidates = append(candidates, rel)
281
+ }
282
+ }
283
+ if rel, ok := suffixAfterPathMarker(stem, []string{"test"}, true); ok {
284
+ candidates = append(candidates, rel)
285
+ }
286
+ return append(candidates, stem)
287
+ }
288
+
289
+ func suffixAfterPathMarker(stem string, marker []string, includeMarker bool) (string, bool) {
290
+ segments := strings.Split(filepath.ToSlash(stem), "/")
291
+ for i := len(segments) - len(marker); i >= 0; i-- {
292
+ matched := true
293
+ for j := range marker {
294
+ if segments[i+j] != marker[j] {
295
+ matched = false
296
+ break
297
+ }
298
+ }
299
+ if !matched {
300
+ continue
301
+ }
302
+ start := i + len(marker)
303
+ if includeMarker {
304
+ start = i
305
+ }
306
+ if start >= len(segments) {
307
+ return "", false
308
+ }
309
+ return strings.Join(segments[start:], "/"), true
310
+ }
311
+ return "", false
312
+ }
313
+
314
+ func spliceNativeCall(text string, r nativeRewrite) (string, bool, error) {
315
+ for _, target := range candidateNativeTargets(r) {
316
+ searchFrom := 0
317
+ for {
318
+ hit, ok := indexNativeFlexibleCall(text, target, r, searchFrom)
319
+ if !ok {
320
+ break
321
+ }
322
+ closePos, ok := matchNativeParen(text, hit.paren)
323
+ if !ok {
324
+ searchFrom = advanceNativeSearch(searchFrom, hit)
325
+ continue
326
+ }
327
+ if r.ExpectedArgumentCount != nil &&
328
+ countNativeArguments(text[hit.paren+1:closePos]) != *r.ExpectedArgumentCount {
329
+ searchFrom = advanceNativeSearch(searchFrom, hit)
330
+ continue
331
+ }
332
+ current := strings.TrimSpace(text[hit.paren+1 : closePos])
333
+ if r.ExpectedArgumentsText != "" && compactNativeArgumentText(current) != compactNativeArgumentText(r.ExpectedArgumentsText) {
334
+ searchFrom = advanceNativeSearch(searchFrom, hit)
335
+ continue
336
+ }
337
+ if len(r.AppendArguments) != 0 {
338
+ next := strings.Join(r.AppendArguments, ", ")
339
+ if current != "" {
340
+ next = current + ", " + next
341
+ }
342
+ replaced := text[:hit.paren+1] + next + text[closePos:]
343
+ return replaced, true, nil
344
+ }
345
+ if r.ConsumeParens {
346
+ replaced := text[:hit.start] + r.Replacement + text[closePos+1:]
347
+ return replaced, true, nil
348
+ }
349
+ replaced := text[:hit.start] + r.Replacement + text[hit.paren:]
350
+ return replaced, true, nil
351
+ }
352
+ }
353
+ return text, false, nil
354
+ }
355
+
356
+ func advanceNativeSearch(current int, hit nativeCallHit) int {
357
+ next := hit.paren + 1
358
+ if next <= current {
359
+ return current + 1
360
+ }
361
+ return next
362
+ }
363
+
364
+ func compactNativeArgumentText(text string) string {
365
+ var builder strings.Builder
366
+ inString := byte(0)
367
+ escaped := false
368
+ for i := 0; i < len(text); i++ {
369
+ ch := text[i]
370
+ if inString != 0 {
371
+ builder.WriteByte(ch)
372
+ if escaped {
373
+ escaped = false
374
+ } else if ch == '\\' {
375
+ escaped = true
376
+ } else if ch == inString {
377
+ inString = 0
378
+ }
379
+ continue
380
+ }
381
+ switch ch {
382
+ case '"', '\'', '`':
383
+ inString = ch
384
+ builder.WriteByte(ch)
385
+ case ' ', '\t', '\r', '\n':
386
+ continue
387
+ default:
388
+ builder.WriteByte(ch)
389
+ }
390
+ }
391
+ return builder.String()
392
+ }
393
+
394
+ type nativeCallHit struct {
395
+ start int
396
+ paren int
397
+ }
398
+
399
+ func indexNativeFlexibleCall(text string, target string, r nativeRewrite, searchFrom int) (nativeCallHit, bool) {
400
+ parts := strings.Split(target, ".")
401
+ if len(parts) == 0 || parts[0] == "" {
402
+ return nativeCallHit{}, false
403
+ }
404
+ root := parts[0]
405
+ parts = parts[1:]
406
+ start := searchFrom
407
+ if start < 0 {
408
+ start = 0
409
+ }
410
+ for {
411
+ hit := strings.Index(text[start:], root)
412
+ if hit < 0 {
413
+ return nativeCallHit{}, false
414
+ }
415
+ pos := start + hit
416
+ if pos > 0 && isNativeIdentifierPart(rune(text[pos-1])) {
417
+ start = pos + 1
418
+ continue
419
+ }
420
+ cursor := pos + len(root)
421
+ ok := true
422
+ for _, part := range parts {
423
+ cursor = skipNativeWhitespace(text, cursor)
424
+ if cursor >= len(text) || text[cursor] != '.' {
425
+ ok = false
426
+ break
427
+ }
428
+ cursor++
429
+ cursor = skipNativeWhitespace(text, cursor)
430
+ if !strings.HasPrefix(text[cursor:], part) {
431
+ ok = false
432
+ break
433
+ }
434
+ cursor += len(part)
435
+ }
436
+ cursor = skipNativeWhitespace(text, cursor)
437
+ if ok && cursor < len(text) && text[cursor] == '(' {
438
+ return nativeCallHit{start: pos, paren: cursor}, true
439
+ }
440
+ if ok {
441
+ if wrappedStart, paren, wrapped := matchNativeCommaWrappedCall(text, pos, cursor); wrapped {
442
+ return nativeCallHit{start: wrappedStart, paren: paren}, true
443
+ }
444
+ }
445
+ start = pos + 1
446
+ }
447
+ }
448
+
449
+ func skipNativeWhitespace(text string, pos int) int {
450
+ for pos < len(text) {
451
+ switch text[pos] {
452
+ case ' ', '\t', '\r', '\n':
453
+ pos++
454
+ default:
455
+ return pos
456
+ }
457
+ }
458
+ return pos
459
+ }
460
+
461
+ func countNativeArguments(text string) int {
462
+ text = strings.TrimSpace(text)
463
+ if text == "" {
464
+ return 0
465
+ }
466
+ count := 1
467
+ depth := 0
468
+ // templateDepths records the bracket depth at which each currently-open
469
+ // `${...}` substitution started. When depth drops back to that level on a
470
+ // '}' token, we pop and re-enter template-string mode.
471
+ templateDepths := []int{}
472
+ i := 0
473
+ for i < len(text) {
474
+ ch := text[i]
475
+ switch ch {
476
+ case '(', '[', '{':
477
+ depth++
478
+ case ')', ']':
479
+ if depth > 0 {
480
+ depth--
481
+ }
482
+ case '}':
483
+ if depth > 0 {
484
+ depth--
485
+ }
486
+ if n := len(templateDepths); n > 0 && templateDepths[n-1] == depth {
487
+ templateDepths = templateDepths[:n-1]
488
+ i++
489
+ skipTemplateLiteral(text, &i, &templateDepths, &depth)
490
+ continue
491
+ }
492
+ case '"', '\'':
493
+ i++
494
+ for i < len(text) && text[i] != ch {
495
+ if text[i] == '\\' && i+1 < len(text) {
496
+ i++
497
+ }
498
+ i++
499
+ }
500
+ case '`':
501
+ i++
502
+ skipTemplateLiteral(text, &i, &templateDepths, &depth)
503
+ continue
504
+ case ',':
505
+ if depth == 0 {
506
+ count++
507
+ }
508
+ }
509
+ i++
510
+ }
511
+ return count
512
+ }
513
+
514
+ // skipTemplateLiteral advances i through a template-literal body. It exits
515
+ // either at the matching closing backtick (consuming it) or at the opening
516
+ // of a `${...}` substitution, in which case the caller resumes regular
517
+ // expression parsing and templateDepths records that we owe a return to
518
+ // template-string mode on the matching '}'.
519
+ func skipTemplateLiteral(text string, i *int, templateDepths *[]int, depth *int) {
520
+ for *i < len(text) {
521
+ c := text[*i]
522
+ if c == '\\' && *i+1 < len(text) {
523
+ *i += 2
524
+ continue
525
+ }
526
+ if c == '`' {
527
+ *i++
528
+ return
529
+ }
530
+ if c == '$' && *i+1 < len(text) && text[*i+1] == '{' {
531
+ *templateDepths = append(*templateDepths, *depth)
532
+ *depth++
533
+ *i += 2
534
+ return
535
+ }
536
+ *i++
537
+ }
538
+ }
539
+
540
+ func candidateNativeRoots(root string) []string {
541
+ return []string{
542
+ root,
543
+ root + "_1.default",
544
+ root + "_2.default",
545
+ root + ".default",
546
+ root + "_1",
547
+ root + "_2",
548
+ }
549
+ }
550
+
551
+ func candidateNativeTargets(r nativeRewrite) []string {
552
+ seen := map[string]bool{}
553
+ output := []string{}
554
+ add := func(value string) {
555
+ value = strings.TrimSpace(value)
556
+ if value == "" || seen[value] {
557
+ return
558
+ }
559
+ seen[value] = true
560
+ output = append(output, value)
561
+ }
562
+ for _, candidate := range r.TargetExpressionCandidates {
563
+ add(candidate)
564
+ }
565
+ if r.Method == "" && len(r.Namespaces) == 0 {
566
+ for _, root := range candidateNativeRoots(r.RootName) {
567
+ add(root)
568
+ }
569
+ return output
570
+ }
571
+ for _, root := range candidateNativeRoots(r.RootName) {
572
+ parts := []string{root}
573
+ parts = append(parts, r.Namespaces...)
574
+ if r.Method != "" {
575
+ parts = append(parts, r.Method)
576
+ }
577
+ add(strings.Join(parts, "."))
578
+ }
579
+ return output
580
+ }
581
+
582
+ func matchNativeCommaWrappedCall(text string, exprStart int, exprEnd int) (int, int, bool) {
583
+ left := exprStart - 1
584
+ for left >= 0 && (text[left] == ' ' || text[left] == '\t' || text[left] == '\r' || text[left] == '\n') {
585
+ left--
586
+ }
587
+ if left < 0 || text[left] != ',' {
588
+ return 0, 0, false
589
+ }
590
+ left--
591
+ for left >= 0 && (text[left] == ' ' || text[left] == '\t' || text[left] == '\r' || text[left] == '\n') {
592
+ left--
593
+ }
594
+ if left < 0 || text[left] != '0' {
595
+ return 0, 0, false
596
+ }
597
+ left--
598
+ for left >= 0 && (text[left] == ' ' || text[left] == '\t' || text[left] == '\r' || text[left] == '\n') {
599
+ left--
600
+ }
601
+ if left < 0 || text[left] != '(' {
602
+ return 0, 0, false
603
+ }
604
+ right := skipNativeWhitespace(text, exprEnd)
605
+ if right >= len(text) || text[right] != ')' {
606
+ return 0, 0, false
607
+ }
608
+ paren := skipNativeWhitespace(text, right+1)
609
+ if paren >= len(text) || text[paren] != '(' {
610
+ return 0, 0, false
611
+ }
612
+ return left, paren, true
613
+ }
614
+
615
+ func joinNativeRootAndNamespaces(r nativeRewrite) string {
616
+ if len(r.Namespaces) == 0 {
617
+ return r.RootName
618
+ }
619
+ return r.RootName + "." + strings.Join(r.Namespaces, ".")
620
+ }
621
+
622
+ func matchNativeParen(text string, pos int) (int, bool) {
623
+ if pos >= len(text) || text[pos] != '(' {
624
+ return 0, false
625
+ }
626
+ depth := 1
627
+ for i := pos + 1; i < len(text); i++ {
628
+ switch text[i] {
629
+ case '(':
630
+ depth++
631
+ case ')':
632
+ depth--
633
+ if depth == 0 {
634
+ return i, true
635
+ }
636
+ case '"', '\'', '`':
637
+ q := text[i]
638
+ j := i + 1
639
+ for j < len(text) && text[j] != q {
640
+ if text[j] == '\\' {
641
+ j++
642
+ }
643
+ j++
644
+ }
645
+ i = j
646
+ }
647
+ }
648
+ return 0, false
649
+ }
650
+
651
+ func isNativeIdentifierPart(r rune) bool {
652
+ return r == '_' || r == '$' || unicode.IsLetter(r) || unicode.IsDigit(r)
653
+ }
654
+
655
+ func insertNativeRewriteSentinel(text string) string {
656
+ for _, prefix := range []string{"\"use strict\";\n", "'use strict';\n"} {
657
+ if strings.HasPrefix(text, prefix) {
658
+ return prefix + "/* @ttsc-rewritten */\n" + text[len(prefix):]
659
+ }
660
+ }
661
+ return "/* @ttsc-rewritten */\n" + text
662
+ }