@nestia/core 11.2.0 → 12.0.0-dev.20260520.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 (171) 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 +2 -5
  46. package/lib/transform.js +48 -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/build.go +434 -0
  54. package/native/cmd/ttsc-nestia/cleanup.go +408 -0
  55. package/native/cmd/ttsc-nestia/core_querify.go +227 -0
  56. package/native/cmd/ttsc-nestia/core_transform.go +1713 -0
  57. package/native/cmd/ttsc-nestia/core_websocket.go +115 -0
  58. package/native/cmd/ttsc-nestia/main.go +72 -0
  59. package/native/cmd/ttsc-nestia/path_rewrite.go +285 -0
  60. package/native/cmd/ttsc-nestia/printer.go +244 -0
  61. package/native/cmd/ttsc-nestia/rewrite.go +662 -0
  62. package/native/cmd/ttsc-nestia/sdk_metadata_json.go +327 -0
  63. package/native/cmd/ttsc-nestia/sdk_transform.go +1541 -0
  64. package/native/cmd/ttsc-nestia/transform.go +387 -0
  65. package/native/cmd/ttsc-nestia/typia_fast.go +326 -0
  66. package/native/cmd/ttsc-nestia/typia_replacement.go +24 -0
  67. package/native/go.mod +32 -0
  68. package/native/go.sum +54 -0
  69. package/native/plugin/plan.go +102 -0
  70. package/native/transform.cjs +21 -0
  71. package/package.json +27 -22
  72. package/src/decorators/NoTransformConfigurationError.ts +5 -2
  73. package/src/decorators/internal/load_controller.ts +50 -19
  74. package/src/decorators/internal/validate_request_query.ts +21 -2
  75. package/src/transform.ts +82 -35
  76. package/lib/decorators/internal/NoTransformConfigureError.d.ts +0 -1
  77. package/lib/decorators/internal/NoTransformConfigureError.js +0 -7
  78. package/lib/decorators/internal/NoTransformConfigureError.js.map +0 -1
  79. package/lib/options/INestiaTransformOptions.d.ts +0 -13
  80. package/lib/options/INestiaTransformOptions.js +0 -3
  81. package/lib/options/INestiaTransformOptions.js.map +0 -1
  82. package/lib/options/INestiaTransformProject.d.ts +0 -5
  83. package/lib/options/INestiaTransformProject.js +0 -3
  84. package/lib/options/INestiaTransformProject.js.map +0 -1
  85. package/lib/programmers/PlainBodyProgrammer.d.ts +0 -9
  86. package/lib/programmers/PlainBodyProgrammer.js +0 -58
  87. package/lib/programmers/PlainBodyProgrammer.js.map +0 -1
  88. package/lib/programmers/TypedBodyProgrammer.d.ts +0 -9
  89. package/lib/programmers/TypedBodyProgrammer.js +0 -94
  90. package/lib/programmers/TypedBodyProgrammer.js.map +0 -1
  91. package/lib/programmers/TypedFormDataBodyProgrammer.d.ts +0 -9
  92. package/lib/programmers/TypedFormDataBodyProgrammer.js +0 -65
  93. package/lib/programmers/TypedFormDataBodyProgrammer.js.map +0 -1
  94. package/lib/programmers/TypedHeadersProgrammer.d.ts +0 -9
  95. package/lib/programmers/TypedHeadersProgrammer.js +0 -38
  96. package/lib/programmers/TypedHeadersProgrammer.js.map +0 -1
  97. package/lib/programmers/TypedParamProgrammer.d.ts +0 -10
  98. package/lib/programmers/TypedParamProgrammer.js +0 -32
  99. package/lib/programmers/TypedParamProgrammer.js.map +0 -1
  100. package/lib/programmers/TypedQueryBodyProgrammer.d.ts +0 -9
  101. package/lib/programmers/TypedQueryBodyProgrammer.js +0 -74
  102. package/lib/programmers/TypedQueryBodyProgrammer.js.map +0 -1
  103. package/lib/programmers/TypedQueryProgrammer.d.ts +0 -9
  104. package/lib/programmers/TypedQueryProgrammer.js +0 -75
  105. package/lib/programmers/TypedQueryProgrammer.js.map +0 -1
  106. package/lib/programmers/TypedQueryRouteProgrammer.d.ts +0 -9
  107. package/lib/programmers/TypedQueryRouteProgrammer.js +0 -75
  108. package/lib/programmers/TypedQueryRouteProgrammer.js.map +0 -1
  109. package/lib/programmers/TypedRouteProgrammer.d.ts +0 -9
  110. package/lib/programmers/TypedRouteProgrammer.js +0 -79
  111. package/lib/programmers/TypedRouteProgrammer.js.map +0 -1
  112. package/lib/programmers/http/HttpAssertQuerifyProgrammer.d.ts +0 -9
  113. package/lib/programmers/http/HttpAssertQuerifyProgrammer.js +0 -39
  114. package/lib/programmers/http/HttpAssertQuerifyProgrammer.js.map +0 -1
  115. package/lib/programmers/http/HttpIsQuerifyProgrammer.d.ts +0 -9
  116. package/lib/programmers/http/HttpIsQuerifyProgrammer.js +0 -36
  117. package/lib/programmers/http/HttpIsQuerifyProgrammer.js.map +0 -1
  118. package/lib/programmers/http/HttpQuerifyProgrammer.d.ts +0 -9
  119. package/lib/programmers/http/HttpQuerifyProgrammer.js +0 -50
  120. package/lib/programmers/http/HttpQuerifyProgrammer.js.map +0 -1
  121. package/lib/programmers/http/HttpValidateQuerifyProgrammer.d.ts +0 -9
  122. package/lib/programmers/http/HttpValidateQuerifyProgrammer.js +0 -40
  123. package/lib/programmers/http/HttpValidateQuerifyProgrammer.js.map +0 -1
  124. package/lib/programmers/internal/CoreMetadataUtil.d.ts +0 -5
  125. package/lib/programmers/internal/CoreMetadataUtil.js +0 -19
  126. package/lib/programmers/internal/CoreMetadataUtil.js.map +0 -1
  127. package/lib/transformers/FileTransformer.d.ts +0 -5
  128. package/lib/transformers/FileTransformer.js +0 -80
  129. package/lib/transformers/FileTransformer.js.map +0 -1
  130. package/lib/transformers/MethodTransformer.d.ts +0 -8
  131. package/lib/transformers/MethodTransformer.js +0 -58
  132. package/lib/transformers/MethodTransformer.js.map +0 -1
  133. package/lib/transformers/NodeTransformer.d.ts +0 -8
  134. package/lib/transformers/NodeTransformer.js +0 -24
  135. package/lib/transformers/NodeTransformer.js.map +0 -1
  136. package/lib/transformers/ParameterDecoratorTransformer.d.ts +0 -9
  137. package/lib/transformers/ParameterDecoratorTransformer.js +0 -104
  138. package/lib/transformers/ParameterDecoratorTransformer.js.map +0 -1
  139. package/lib/transformers/ParameterTransformer.d.ts +0 -8
  140. package/lib/transformers/ParameterTransformer.js +0 -37
  141. package/lib/transformers/ParameterTransformer.js.map +0 -1
  142. package/lib/transformers/TypedRouteTransformer.d.ts +0 -9
  143. package/lib/transformers/TypedRouteTransformer.js +0 -68
  144. package/lib/transformers/TypedRouteTransformer.js.map +0 -1
  145. package/lib/transformers/WebSocketRouteTransformer.d.ts +0 -9
  146. package/lib/transformers/WebSocketRouteTransformer.js +0 -72
  147. package/lib/transformers/WebSocketRouteTransformer.js.map +0 -1
  148. package/src/decorators/internal/NoTransformConfigureError.ts +0 -2
  149. package/src/options/INestiaTransformOptions.ts +0 -34
  150. package/src/options/INestiaTransformProject.ts +0 -10
  151. package/src/programmers/PlainBodyProgrammer.ts +0 -72
  152. package/src/programmers/TypedBodyProgrammer.ts +0 -148
  153. package/src/programmers/TypedFormDataBodyProgrammer.ts +0 -118
  154. package/src/programmers/TypedHeadersProgrammer.ts +0 -65
  155. package/src/programmers/TypedParamProgrammer.ts +0 -33
  156. package/src/programmers/TypedQueryBodyProgrammer.ts +0 -113
  157. package/src/programmers/TypedQueryProgrammer.ts +0 -115
  158. package/src/programmers/TypedQueryRouteProgrammer.ts +0 -107
  159. package/src/programmers/TypedRouteProgrammer.ts +0 -103
  160. package/src/programmers/http/HttpAssertQuerifyProgrammer.ts +0 -74
  161. package/src/programmers/http/HttpIsQuerifyProgrammer.ts +0 -77
  162. package/src/programmers/http/HttpQuerifyProgrammer.ts +0 -110
  163. package/src/programmers/http/HttpValidateQuerifyProgrammer.ts +0 -78
  164. package/src/programmers/internal/CoreMetadataUtil.ts +0 -21
  165. package/src/transformers/FileTransformer.ts +0 -109
  166. package/src/transformers/MethodTransformer.ts +0 -103
  167. package/src/transformers/NodeTransformer.ts +0 -23
  168. package/src/transformers/ParameterDecoratorTransformer.ts +0 -143
  169. package/src/transformers/ParameterTransformer.ts +0 -57
  170. package/src/transformers/TypedRouteTransformer.ts +0 -85
  171. package/src/transformers/WebSocketRouteTransformer.ts +0 -120
@@ -0,0 +1,1713 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "regexp"
8
+ "runtime/debug"
9
+ "sort"
10
+ "strconv"
11
+ "strings"
12
+ "sync"
13
+ "unicode"
14
+
15
+ shimast "github.com/microsoft/typescript-go/shim/ast"
16
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
17
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
18
+ "github.com/samchon/nestia/packages/core/native/plugin"
19
+ "github.com/samchon/ttsc/packages/ttsc/driver"
20
+ nativecontext "github.com/samchon/typia/packages/typia/native/core/context"
21
+ nativefactories "github.com/samchon/typia/packages/typia/native/core/factories"
22
+ nativeprogrammers "github.com/samchon/typia/packages/typia/native/core/programmers"
23
+ nativehttp "github.com/samchon/typia/packages/typia/native/core/programmers/http"
24
+ nativejson "github.com/samchon/typia/packages/typia/native/core/programmers/json"
25
+ nativellm "github.com/samchon/typia/packages/typia/native/core/programmers/llm"
26
+ nativemisc "github.com/samchon/typia/packages/typia/native/core/programmers/misc"
27
+ schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
28
+ )
29
+
30
+ type nestiaCoreOptions struct {
31
+ Validate string
32
+ Stringify string
33
+ StringifyNull bool
34
+ Llm bool
35
+ LlmStrict bool
36
+ }
37
+
38
+ type nestiaCoreSite struct {
39
+ File *shimast.SourceFile
40
+ FilePath string
41
+ Call *shimast.CallExpression
42
+ Modulo *shimast.Node
43
+ Kind string
44
+ Type *shimchecker.Type
45
+ ArgCount int
46
+ Segments []string
47
+ Arguments []string
48
+ }
49
+
50
+ type nestiaCoreTransformState struct {
51
+ prog *driver.Program
52
+ options nestiaCoreOptions
53
+ cache map[nestiaCoreCacheKey][]string
54
+ cacheHits int
55
+ cacheMisses int
56
+ }
57
+
58
+ type nestiaCoreCacheKey struct {
59
+ Kind string
60
+ Type *shimchecker.Type
61
+ TypeName string
62
+ Modulo string
63
+ Validate string
64
+ Stringify string
65
+ StringifyNull bool
66
+ Llm bool
67
+ LlmStrict bool
68
+ ArgCount int
69
+ AllowOptional bool
70
+ }
71
+
72
+ func newNestiaCoreTransformState(prog *driver.Program, options nestiaCoreOptions) *nestiaCoreTransformState {
73
+ return &nestiaCoreTransformState{
74
+ prog: prog,
75
+ options: options,
76
+ cache: map[nestiaCoreCacheKey][]string{},
77
+ }
78
+ }
79
+
80
+ var nestiaCoreFactory = shimast.NewNodeFactory(shimast.NodeFactoryHooks{})
81
+
82
+ const nestiaCoreKindDecorator = shimast.KindDecorator
83
+
84
+ type nestiaCoreFileContext struct {
85
+ file *shimast.SourceFile
86
+ coreImports map[string]string
87
+ }
88
+
89
+ func readNestiaCoreOptions(plan plugin.Plan) nestiaCoreOptions {
90
+ options := nestiaCoreOptions{}
91
+ for _, entry := range plan.Entries {
92
+ if entry.Name != "@nestia/core" && !strings.Contains(entry.Transform, "@nestia/core") {
93
+ continue
94
+ }
95
+ if value, ok := entry.Config["validate"].(string); ok {
96
+ options.Validate = value
97
+ }
98
+ if value, ok := entry.Config["stringify"]; ok {
99
+ if value == nil {
100
+ options.StringifyNull = true
101
+ } else if text, ok := value.(string); ok {
102
+ options.Stringify = text
103
+ }
104
+ }
105
+ if value, ok := entry.Config["llm"]; ok {
106
+ switch v := value.(type) {
107
+ case bool:
108
+ options.Llm = v
109
+ case map[string]any:
110
+ options.Llm = true
111
+ if strict, ok := v["strict"].(bool); ok {
112
+ options.LlmStrict = strict
113
+ }
114
+ }
115
+ }
116
+ }
117
+ return options
118
+ }
119
+
120
+ func collectNestiaCoreSourceRewriteMap(
121
+ prog *driver.Program,
122
+ plan plugin.Plan,
123
+ onlyFile string,
124
+ ) (map[string][]transformSourceRewrite, []typiaTransformDiagnostic) {
125
+ if plan.Core == false {
126
+ return map[string][]transformSourceRewrite{}, nil
127
+ }
128
+ options := readNestiaCoreOptions(plan)
129
+ sites, diagnostics := collectNestiaCoreSites(newNestiaCoreTransformState(prog, options))
130
+ rewrites := map[string][]transformSourceRewrite{}
131
+ for _, site := range sites {
132
+ if onlyFile != "" && filepath.ToSlash(site.FilePath) != filepath.ToSlash(onlyFile) {
133
+ continue
134
+ }
135
+ source, ok := sourceFileText(site.File)
136
+ if !ok {
137
+ diagnostics = append(diagnostics, nestiaCoreDiagnostic(site, "source text is unavailable"))
138
+ continue
139
+ }
140
+ open, close, ok := callArgumentBounds(source, site.Call)
141
+ if !ok {
142
+ diagnostics = append(diagnostics, nestiaCoreDiagnostic(site, "failed to locate decorator arguments"))
143
+ continue
144
+ }
145
+ replacement := appendArgumentsText(source[open+1:close], site.Arguments)
146
+ rewrites[filepath.ToSlash(site.FilePath)] = append(rewrites[filepath.ToSlash(site.FilePath)], transformSourceRewrite{
147
+ start: open + 1,
148
+ end: close,
149
+ replacement: replacement,
150
+ })
151
+ }
152
+ return rewrites, diagnostics
153
+ }
154
+
155
+ func collectNestiaCoreBuildRewrites(
156
+ prog *driver.Program,
157
+ plan plugin.Plan,
158
+ rewrites *nativeRewriteSet,
159
+ ) []typiaTransformDiagnostic {
160
+ if plan.Core == false {
161
+ return nil
162
+ }
163
+ options := readNestiaCoreOptions(plan)
164
+ sites, diagnostics := collectNestiaCoreSites(newNestiaCoreTransformState(prog, options))
165
+ for _, site := range sites {
166
+ expectedArgumentCount := site.ArgCount
167
+ expectedArgumentsText := nestiaCoreStableOriginalArgumentText(site)
168
+ rewrites.Add(nativeRewrite{
169
+ FilePath: site.FilePath,
170
+ RootName: site.Segments[0],
171
+ Namespaces: site.Segments[1:],
172
+ AppendArguments: site.Arguments,
173
+ TargetExpressionCandidates: nestiaCoreTargetCandidates(prog, site),
174
+ SourceStart: site.Call.AsNode().Pos(),
175
+ ExpectedArgumentCount: &expectedArgumentCount,
176
+ ExpectedArgumentsText: expectedArgumentsText,
177
+ })
178
+ }
179
+ return diagnostics
180
+ }
181
+
182
+ func nestiaCoreOriginalArgumentText(site nestiaCoreSite) string {
183
+ source, ok := sourceFileText(site.File)
184
+ if !ok {
185
+ return ""
186
+ }
187
+ open, close, ok := callArgumentBounds(source, site.Call)
188
+ if !ok {
189
+ return ""
190
+ }
191
+ return strings.TrimSpace(source[open+1 : close])
192
+ }
193
+
194
+ func nestiaCoreStableOriginalArgumentText(site nestiaCoreSite) string {
195
+ text := nestiaCoreOriginalArgumentText(site)
196
+ if strings.Contains(text, "=>") || strings.Contains(text, "function") {
197
+ return ""
198
+ }
199
+ return text
200
+ }
201
+
202
+ func collectNestiaCoreSites(state *nestiaCoreTransformState) ([]nestiaCoreSite, []typiaTransformDiagnostic) {
203
+ sites := []nestiaCoreSite{}
204
+ diagnostics := []typiaTransformDiagnostic{}
205
+ prog := state.prog
206
+ if nestiaCoreStrictMode(prog) == false {
207
+ diagnostics = append(diagnostics, nestiaCoreGlobalDiagnostic("@nestia/core", "strict mode is required."))
208
+ }
209
+ for _, file := range prog.SourceFiles() {
210
+ if file == nil || file.IsDeclarationFile {
211
+ continue
212
+ }
213
+ context := newNestiaCoreFileContext(file)
214
+ file.ForEachChild(func(node *shimast.Node) bool {
215
+ visitNestiaCoreNode(state, context, node, &sites, &diagnostics)
216
+ return false
217
+ })
218
+ }
219
+ if os.Getenv("TTSC_NESTIA_PROFILE") != "" {
220
+ fmt.Fprintf(stderr, "ttsc-nestia profile: core-cache hits=%d misses=%d\n", state.cacheHits, state.cacheMisses)
221
+ }
222
+ return sites, diagnostics
223
+ }
224
+
225
+ func visitNestiaCoreNode(
226
+ state *nestiaCoreTransformState,
227
+ context nestiaCoreFileContext,
228
+ node *shimast.Node,
229
+ sites *[]nestiaCoreSite,
230
+ diagnostics *[]typiaTransformDiagnostic,
231
+ ) {
232
+ if node == nil {
233
+ return
234
+ }
235
+ switch node.Kind {
236
+ case shimast.KindParameter:
237
+ decorators := node.Decorators()
238
+ if len(decorators) == 0 {
239
+ break
240
+ }
241
+ type candidate struct {
242
+ decorator *shimast.Node
243
+ call *shimast.CallExpression
244
+ segments []string
245
+ kind string
246
+ }
247
+ candidates := []candidate{}
248
+ for _, decorator := range decorators {
249
+ call, segments, ok := nestiaCoreRawDecoratorCall(decorator)
250
+ if !ok {
251
+ continue
252
+ }
253
+ canonical := nestiaCoreCanonicalSegments(context, segments)
254
+ kind := nestiaCoreParameterKind(canonical)
255
+ if kind == "" || nestiaCoreDecoratorReference(state.prog, context, decorator, segments, canonical) == false {
256
+ continue
257
+ }
258
+ candidates = append(candidates, candidate{
259
+ decorator: decorator,
260
+ call: call,
261
+ segments: segments,
262
+ kind: kind,
263
+ })
264
+ }
265
+ if len(candidates) == 0 {
266
+ break
267
+ }
268
+ typ := state.prog.Checker.GetTypeAtLocation(node)
269
+ for _, candidate := range candidates {
270
+ site, ok, err := transformNestiaCoreParameterDecorator(
271
+ state,
272
+ context.file,
273
+ candidate.call,
274
+ candidate.segments,
275
+ candidate.kind,
276
+ typ,
277
+ )
278
+ if err != nil {
279
+ *diagnostics = append(*diagnostics, nestiaCoreDiagnostic(site, err.Error()))
280
+ } else if ok {
281
+ *sites = append(*sites, site)
282
+ }
283
+ }
284
+ case shimast.KindMethodDeclaration:
285
+ decorators := node.Decorators()
286
+ if len(decorators) == 0 {
287
+ break
288
+ }
289
+ type candidate struct {
290
+ call *shimast.CallExpression
291
+ segments []string
292
+ kind string
293
+ }
294
+ candidates := []candidate{}
295
+ for _, decorator := range decorators {
296
+ call, segments, ok := nestiaCoreRawDecoratorCall(decorator)
297
+ if !ok {
298
+ continue
299
+ }
300
+ canonical := nestiaCoreCanonicalSegments(context, segments)
301
+ if nestiaCoreDecoratorReference(state.prog, context, decorator, segments, canonical) == false {
302
+ continue
303
+ }
304
+ if len(canonical) != 0 && canonical[len(canonical)-1] == "WebSocketRoute" {
305
+ *diagnostics = append(*diagnostics, validateNestiaCoreWebSocketRoute(state.prog, context, node, call, canonical)...)
306
+ }
307
+ kind := nestiaCoreMethodKind(canonical)
308
+ if kind == "" || nestiaCoreShouldSkipMethodDecorator(state.prog, call) {
309
+ continue
310
+ }
311
+ candidates = append(candidates, candidate{
312
+ call: call,
313
+ segments: segments,
314
+ kind: kind,
315
+ })
316
+ }
317
+ if len(candidates) == 0 {
318
+ break
319
+ }
320
+ typ := nestiaCoreMethodReturnType(state.prog, node)
321
+ if typ != nil {
322
+ for _, candidate := range candidates {
323
+ site, ok, err := transformNestiaCoreMethodDecorator(
324
+ state,
325
+ context.file,
326
+ candidate.call,
327
+ candidate.segments,
328
+ candidate.kind,
329
+ typ,
330
+ )
331
+ if err != nil {
332
+ *diagnostics = append(*diagnostics, nestiaCoreDiagnostic(site, err.Error()))
333
+ } else if ok {
334
+ *sites = append(*sites, site)
335
+ }
336
+ }
337
+ }
338
+ }
339
+ node.ForEachChild(func(child *shimast.Node) bool {
340
+ visitNestiaCoreNode(state, context, child, sites, diagnostics)
341
+ return false
342
+ })
343
+ }
344
+
345
+ func transformNestiaCoreParameterDecorator(
346
+ state *nestiaCoreTransformState,
347
+ file *shimast.SourceFile,
348
+ call *shimast.CallExpression,
349
+ segments []string,
350
+ kind string,
351
+ typ *shimchecker.Type,
352
+ ) (nestiaCoreSite, bool, error) {
353
+ modulo := nestiaCoreModuloNode(call.Expression)
354
+ arguments, ok, err := state.parameterArguments(call, segments, modulo, kind, typ)
355
+ if err != nil || !ok {
356
+ return nestiaCoreSite{
357
+ File: file,
358
+ FilePath: file.FileName(),
359
+ Call: call,
360
+ Modulo: modulo,
361
+ Kind: kind,
362
+ Type: typ,
363
+ Segments: segments,
364
+ }, ok, err
365
+ }
366
+ return nestiaCoreSite{
367
+ File: file,
368
+ FilePath: file.FileName(),
369
+ Call: call,
370
+ Modulo: modulo,
371
+ Kind: kind,
372
+ Type: typ,
373
+ ArgCount: nestiaCoreArgumentCount(call),
374
+ Segments: segments,
375
+ Arguments: arguments,
376
+ }, true, nil
377
+ }
378
+
379
+ func transformNestiaCoreMethodDecorator(
380
+ state *nestiaCoreTransformState,
381
+ file *shimast.SourceFile,
382
+ call *shimast.CallExpression,
383
+ segments []string,
384
+ kind string,
385
+ typ *shimchecker.Type,
386
+ ) (nestiaCoreSite, bool, error) {
387
+ modulo := nestiaCoreModuloNode(call.Expression)
388
+ arguments, err := state.methodArguments(file, segments, modulo, kind, typ, nestiaCoreArgumentCount(call))
389
+ if err != nil {
390
+ return nestiaCoreSite{
391
+ File: file,
392
+ FilePath: file.FileName(),
393
+ Call: call,
394
+ Modulo: modulo,
395
+ Kind: kind,
396
+ Type: typ,
397
+ Segments: segments,
398
+ }, false, err
399
+ }
400
+ return nestiaCoreSite{
401
+ File: file,
402
+ FilePath: file.FileName(),
403
+ Call: call,
404
+ Modulo: modulo,
405
+ Kind: kind,
406
+ Type: typ,
407
+ ArgCount: nestiaCoreArgumentCount(call),
408
+ Segments: segments,
409
+ Arguments: arguments,
410
+ }, true, nil
411
+ }
412
+
413
+ func (state *nestiaCoreTransformState) parameterArguments(
414
+ call *shimast.CallExpression,
415
+ segments []string,
416
+ modulo *shimast.Node,
417
+ kind string,
418
+ typ *shimchecker.Type,
419
+ ) ([]string, bool, error) {
420
+ key := state.cacheKey(segments, kind, typ, nestiaCoreArgumentCount(call), kind == "TypedQuery")
421
+ return state.cachedArguments(key, func() ([]string, bool, error) {
422
+ return nestiaCoreParameterArguments(state.prog, state.options, call, modulo, kind, typ)
423
+ })
424
+ }
425
+
426
+ func (state *nestiaCoreTransformState) methodArguments(
427
+ file *shimast.SourceFile,
428
+ segments []string,
429
+ modulo *shimast.Node,
430
+ kind string,
431
+ typ *shimchecker.Type,
432
+ argCount int,
433
+ ) ([]string, error) {
434
+ key := state.cacheKey(segments, kind, typ, argCount, kind == "TypedQueryRoute")
435
+ arguments, _, err := state.cachedArguments(key, func() ([]string, bool, error) {
436
+ arg, err := safeNestiaCoreGenerate(func() (*shimast.Node, error) {
437
+ switch kind {
438
+ case "TypedQueryRoute":
439
+ return nestiaCoreGenerateTypedQueryRoute(state.prog, state.options, modulo, typ), nil
440
+ default:
441
+ return nestiaCoreGenerateTypedRoute(state.prog, state.options, modulo, typ), nil
442
+ }
443
+ }, state.prog, file, false)
444
+ if err != nil {
445
+ return nil, false, err
446
+ }
447
+ return []string{arg}, true, nil
448
+ })
449
+ return arguments, err
450
+ }
451
+
452
+ func (state *nestiaCoreTransformState) cachedArguments(
453
+ key nestiaCoreCacheKey,
454
+ generate func() ([]string, bool, error),
455
+ ) ([]string, bool, error) {
456
+ if cached, ok := state.cache[key]; ok {
457
+ state.cacheHits++
458
+ return append([]string(nil), cached...), true, nil
459
+ }
460
+ state.cacheMisses++
461
+ arguments, ok, err := generate()
462
+ if err != nil || ok == false {
463
+ return arguments, ok, err
464
+ }
465
+ state.cache[key] = append([]string(nil), arguments...)
466
+ return append([]string(nil), arguments...), true, nil
467
+ }
468
+
469
+ func (state *nestiaCoreTransformState) cacheKey(
470
+ segments []string,
471
+ kind string,
472
+ typ *shimchecker.Type,
473
+ argCount int,
474
+ allowOptional bool,
475
+ ) nestiaCoreCacheKey {
476
+ return nestiaCoreCacheKey{
477
+ Kind: kind,
478
+ Type: typ,
479
+ TypeName: nestiaCoreTypeNameText(state.prog, typ),
480
+ Modulo: strings.Join(segments, "."),
481
+ Validate: state.options.Validate,
482
+ Stringify: state.options.Stringify,
483
+ StringifyNull: state.options.StringifyNull,
484
+ Llm: state.options.Llm,
485
+ LlmStrict: state.options.LlmStrict,
486
+ ArgCount: argCount,
487
+ AllowOptional: allowOptional,
488
+ }
489
+ }
490
+
491
+ func nestiaCoreRawDecoratorCall(decorator *shimast.Node) (*shimast.CallExpression, []string, bool) {
492
+ if decorator == nil || decorator.Kind != nestiaCoreKindDecorator {
493
+ return nil, nil, false
494
+ }
495
+ expression := decorator.AsDecorator().Expression
496
+ if expression == nil || expression.Kind != shimast.KindCallExpression {
497
+ return nil, nil, false
498
+ }
499
+ call := expression.AsCallExpression()
500
+ segments := nestiaCoreExpressionSegments(call.Expression)
501
+ if len(segments) == 0 {
502
+ return nil, nil, false
503
+ }
504
+ return call, segments, true
505
+ }
506
+
507
+ func nestiaCoreDecoratorCall(prog *driver.Program, decorator *shimast.Node) (*shimast.CallExpression, []string, bool) {
508
+ call, segments, ok := nestiaCoreRawDecoratorCall(decorator)
509
+ if !ok {
510
+ return nil, nil, false
511
+ }
512
+ context := newNestiaCoreFileContext(shimast.GetSourceFileOfNode(decorator))
513
+ canonical := nestiaCoreCanonicalSegments(context, segments)
514
+ if nestiaCoreDecoratorReference(prog, context, decorator, segments, canonical) == false {
515
+ return nil, nil, false
516
+ }
517
+ return call, canonical, true
518
+ }
519
+
520
+ func newNestiaCoreFileContext(file *shimast.SourceFile) nestiaCoreFileContext {
521
+ context := nestiaCoreFileContext{
522
+ file: file,
523
+ coreImports: map[string]string{},
524
+ }
525
+ if file == nil || file.Statements == nil {
526
+ return context
527
+ }
528
+ for _, stmt := range file.Statements.Nodes {
529
+ if stmt == nil || stmt.Kind != shimast.KindImportDeclaration {
530
+ continue
531
+ }
532
+ decl := stmt.AsImportDeclaration()
533
+ if decl == nil || decl.ImportClause == nil || decl.ModuleSpecifier == nil || decl.ModuleSpecifier.Kind != shimast.KindStringLiteral {
534
+ continue
535
+ }
536
+ if decl.ModuleSpecifier.Text() != "@nestia/core" {
537
+ continue
538
+ }
539
+ clause := decl.ImportClause.AsImportClause()
540
+ if clause == nil || clause.PhaseModifier == shimast.KindTypeKeyword {
541
+ continue
542
+ }
543
+ if name := clause.Name(); name != nil {
544
+ context.coreImports[name.Text()] = name.Text()
545
+ }
546
+ if clause.NamedBindings == nil || clause.NamedBindings.Kind != shimast.KindNamedImports {
547
+ continue
548
+ }
549
+ named := clause.NamedBindings.AsNamedImports()
550
+ if named == nil || named.Elements == nil {
551
+ continue
552
+ }
553
+ for _, elem := range named.Elements.Nodes {
554
+ if elem == nil {
555
+ continue
556
+ }
557
+ spec := elem.AsImportSpecifier()
558
+ if spec == nil || spec.IsTypeOnly {
559
+ continue
560
+ }
561
+ name := spec.Name()
562
+ if name != nil {
563
+ imported := name.Text()
564
+ if spec.PropertyName != nil {
565
+ imported = spec.PropertyName.Text()
566
+ }
567
+ context.coreImports[name.Text()] = imported
568
+ }
569
+ }
570
+ }
571
+ return context
572
+ }
573
+
574
+ func nestiaCoreCanonicalSegments(context nestiaCoreFileContext, segments []string) []string {
575
+ if len(segments) == 0 {
576
+ return segments
577
+ }
578
+ imported, ok := context.coreImports[segments[0]]
579
+ if !ok || imported == "" || imported == segments[0] {
580
+ return segments
581
+ }
582
+ canonical := append([]string{}, segments...)
583
+ canonical[0] = imported
584
+ return canonical
585
+ }
586
+
587
+ func nestiaCoreDecoratorReference(
588
+ prog *driver.Program,
589
+ context nestiaCoreFileContext,
590
+ decorator *shimast.Node,
591
+ segments []string,
592
+ canonical []string,
593
+ ) bool {
594
+ if len(segments) == 0 {
595
+ return false
596
+ }
597
+ if _, ok := context.coreImports[segments[0]]; ok {
598
+ return true
599
+ }
600
+ if nestiaCorePotentialDecoratorSegments(canonical) == false {
601
+ return false
602
+ }
603
+ return isNestiaCoreCall(prog, decorator.AsDecorator().Expression)
604
+ }
605
+
606
+ func nestiaCorePotentialDecoratorSegments(segments []string) bool {
607
+ return nestiaCoreParameterKind(segments) != "" ||
608
+ nestiaCoreMethodKind(segments) != "" ||
609
+ (len(segments) != 0 && segments[len(segments)-1] == "WebSocketRoute")
610
+ }
611
+
612
+ func isNestiaCoreCall(prog *driver.Program, node *shimast.Node) bool {
613
+ signature := prog.Checker.GetResolvedSignature(node)
614
+ if signature == nil || signature.Declaration() == nil {
615
+ return false
616
+ }
617
+ source := shimast.GetSourceFileOfNode(signature.Declaration())
618
+ if source == nil {
619
+ return false
620
+ }
621
+ location := filepath.ToSlash(source.FileName())
622
+ return strings.Contains(location, "@nestia/core/lib/") ||
623
+ strings.Contains(location, "packages/core/lib/") ||
624
+ strings.Contains(location, "@nestia/core/src/decorators/") ||
625
+ strings.Contains(location, "packages/core/src/decorators/")
626
+ }
627
+
628
+ func isNestiaCoreImportedExpression(node *shimast.Node, segments []string) bool {
629
+ if len(segments) == 0 {
630
+ return false
631
+ }
632
+ source := shimast.GetSourceFileOfNode(node)
633
+ if source == nil || source.Statements == nil {
634
+ return false
635
+ }
636
+ root := segments[0]
637
+ for _, stmt := range source.Statements.Nodes {
638
+ if stmt == nil || stmt.Kind != shimast.KindImportDeclaration {
639
+ continue
640
+ }
641
+ decl := stmt.AsImportDeclaration()
642
+ if decl == nil || decl.ImportClause == nil || decl.ModuleSpecifier == nil || decl.ModuleSpecifier.Kind != shimast.KindStringLiteral {
643
+ continue
644
+ }
645
+ if decl.ModuleSpecifier.Text() != "@nestia/core" {
646
+ continue
647
+ }
648
+ clause := decl.ImportClause.AsImportClause()
649
+ if clause == nil || clause.PhaseModifier == shimast.KindTypeKeyword {
650
+ continue
651
+ }
652
+ if name := clause.Name(); name != nil && name.Text() == root {
653
+ return true
654
+ }
655
+ if clause.NamedBindings == nil || clause.NamedBindings.Kind != shimast.KindNamedImports {
656
+ continue
657
+ }
658
+ named := clause.NamedBindings.AsNamedImports()
659
+ if named == nil || named.Elements == nil {
660
+ continue
661
+ }
662
+ for _, elem := range named.Elements.Nodes {
663
+ if elem == nil {
664
+ continue
665
+ }
666
+ spec := elem.AsImportSpecifier()
667
+ if spec == nil || spec.IsTypeOnly {
668
+ continue
669
+ }
670
+ name := spec.Name()
671
+ if name != nil && name.Text() == root {
672
+ return true
673
+ }
674
+ }
675
+ }
676
+ return false
677
+ }
678
+
679
+ func nestiaCoreParameterKind(segments []string) string {
680
+ suffixes := map[string]string{
681
+ "EncryptedBody": "TypedBody",
682
+ "TypedBody": "TypedBody",
683
+ "TypedHeaders": "TypedHeaders",
684
+ "TypedParam": "TypedParam",
685
+ "TypedQuery": "TypedQuery",
686
+ "TypedQuery.Body": "TypedQueryBody",
687
+ "TypedFormData.Body": "TypedFormDataBody",
688
+ "PlainBody": "PlainBody",
689
+ "WebSocketRoute.Header": "TypedBody",
690
+ "WebSocketRoute.Param": "TypedParam",
691
+ "WebSocketRoute.Query": "TypedQuery",
692
+ }
693
+ for suffix, kind := range suffixes {
694
+ if nestiaCoreSegmentsHaveSuffix(segments, strings.Split(suffix, ".")) {
695
+ return kind
696
+ }
697
+ }
698
+ return ""
699
+ }
700
+
701
+ func nestiaCoreMethodKind(segments []string) string {
702
+ if len(segments) < 2 {
703
+ return ""
704
+ }
705
+ methods := map[string]bool{"Get": true, "Post": true, "Patch": true, "Put": true, "Delete": true}
706
+ if methods[segments[len(segments)-1]] == false {
707
+ return ""
708
+ }
709
+ switch segments[len(segments)-2] {
710
+ case "EncryptedRoute", "TypedRoute":
711
+ return "TypedRoute"
712
+ case "TypedQuery":
713
+ return "TypedQueryRoute"
714
+ default:
715
+ return ""
716
+ }
717
+ }
718
+
719
+ func nestiaCoreParameterArguments(
720
+ prog *driver.Program,
721
+ options nestiaCoreOptions,
722
+ call *shimast.CallExpression,
723
+ modulo *shimast.Node,
724
+ kind string,
725
+ typ *shimchecker.Type,
726
+ ) ([]string, bool, error) {
727
+ argCount := nestiaCoreArgumentCount(call)
728
+ switch kind {
729
+ case "TypedBody", "TypedHeaders", "TypedQuery", "TypedQueryBody", "PlainBody":
730
+ if argCount != 0 {
731
+ return nil, false, nil
732
+ }
733
+ case "TypedParam":
734
+ if argCount != 1 {
735
+ return nil, false, nil
736
+ }
737
+ case "TypedFormDataBody":
738
+ if argCount > 1 {
739
+ return nil, false, nil
740
+ }
741
+ }
742
+ expr, err := safeNestiaCoreGenerate(func() (*shimast.Node, error) {
743
+ switch kind {
744
+ case "TypedBody":
745
+ return nestiaCoreGenerateTypedBody(prog, options, modulo, typ), nil
746
+ case "TypedHeaders":
747
+ return nestiaCoreGenerateTypedHeaders(prog, options, modulo, typ), nil
748
+ case "TypedParam":
749
+ return nestiaCoreGenerateTypedParam(prog, modulo, typ), nil
750
+ case "TypedQuery":
751
+ return nestiaCoreGenerateTypedQuery(prog, options, modulo, typ, true), nil
752
+ case "TypedQueryBody":
753
+ return nestiaCoreGenerateTypedQuery(prog, options, modulo, typ, false), nil
754
+ case "TypedFormDataBody":
755
+ return nestiaCoreGenerateTypedFormDataBody(prog, options, modulo, typ), nil
756
+ case "PlainBody":
757
+ return nestiaCoreGeneratePlainBody(prog, modulo, typ), nil
758
+ default:
759
+ return nil, fmt.Errorf("unsupported parameter decorator %s", kind)
760
+ }
761
+ }, prog, shimast.GetSourceFileOfNode(call.AsNode()), false)
762
+ if err != nil {
763
+ return nil, false, err
764
+ }
765
+ output := []string{}
766
+ if kind == "TypedFormDataBody" && argCount == 0 {
767
+ output = append(output, "undefined")
768
+ }
769
+ output = append(output, expr)
770
+ // TypedParam takes a third `validate?: boolean` argument (see
771
+ // packages/core/src/decorators/TypedParam.ts). When the configured
772
+ // validate mode starts with "validate", emit `true` so the runtime
773
+ // returns the detailed report shape instead of the single-error shape.
774
+ // The legacy TypedParamProgrammer applied the same conditional.
775
+ if kind == "TypedParam" && strings.HasPrefix(options.Validate, "validate") {
776
+ output = append(output, "true")
777
+ }
778
+ return output, true, nil
779
+ }
780
+
781
+ func nestiaCoreGenerateTypedBody(
782
+ prog *driver.Program,
783
+ options nestiaCoreOptions,
784
+ modulo *shimast.Node,
785
+ typ *shimchecker.Type,
786
+ ) *shimast.Node {
787
+ nestiaCoreValidateTypedBody(prog, options, typ)
788
+ context := nestiaCoreTypiaContext(prog, false, false, false)
789
+ name := nestiaCoreTypeName(prog, typ)
790
+ category := options.Validate
791
+ switch category {
792
+ case "assert":
793
+ return nestiaCoreValidatorObject("type", "assert", nativeprogrammers.AssertProgrammer.Write(nativeprogrammers.AssertProgrammer_IProps{
794
+ Context: context, Modulo: modulo, Type: typ, Name: name,
795
+ Config: nativeprogrammers.AssertProgrammer_IConfig{Equals: false, Guard: false},
796
+ }))
797
+ case "is":
798
+ return nestiaCoreValidatorObject("type", "is", nativeprogrammers.IsProgrammer.Write(nativeprogrammers.IsProgrammer_IProps{
799
+ Context: context, Modulo: modulo, Type: typ, Name: name,
800
+ Config: nativeprogrammers.IsProgrammer_IConfig{Equals: false},
801
+ }))
802
+ case "validateEquals":
803
+ return nestiaCoreValidatorObject("type", "validate", nativeprogrammers.ValidateProgrammer.Write(nativeprogrammers.ValidateProgrammer_IProps{
804
+ Context: context, Modulo: modulo, Type: typ, Name: name,
805
+ Config: nativeprogrammers.ValidateProgrammer_IConfig{Equals: true},
806
+ }))
807
+ case "equals":
808
+ return nestiaCoreValidatorObject("type", "is", nativeprogrammers.IsProgrammer.Write(nativeprogrammers.IsProgrammer_IProps{
809
+ Context: context, Modulo: modulo, Type: typ, Name: name,
810
+ Config: nativeprogrammers.IsProgrammer_IConfig{Equals: true},
811
+ }))
812
+ case "assertEquals":
813
+ return nestiaCoreValidatorObject("type", "assert", nativeprogrammers.AssertProgrammer.Write(nativeprogrammers.AssertProgrammer_IProps{
814
+ Context: context, Modulo: modulo, Type: typ, Name: name,
815
+ Config: nativeprogrammers.AssertProgrammer_IConfig{Equals: true, Guard: false},
816
+ }))
817
+ case "assertClone":
818
+ return nestiaCoreValidatorObject("type", "assert", nativemisc.MiscAssertCloneProgrammer.Write(nativecontext.IProgrammerProps{
819
+ Context: context, Modulo: modulo, Type: typ, Name: name,
820
+ }))
821
+ case "validateClone":
822
+ return nestiaCoreValidatorObject("type", "validate", nativemisc.MiscValidateCloneProgrammer.Write(nativecontext.IProgrammerProps{
823
+ Context: context, Modulo: modulo, Type: typ, Name: name,
824
+ }))
825
+ case "assertPrune":
826
+ return nestiaCoreValidatorObject("type", "assert", nativemisc.MiscAssertPruneProgrammer.Write(nativecontext.IProgrammerProps{
827
+ Context: context, Modulo: modulo, Type: typ, Name: name,
828
+ }))
829
+ case "validatePrune":
830
+ return nestiaCoreValidatorObject("type", "validate", nativemisc.MiscValidatePruneProgrammer.Write(nativecontext.IProgrammerProps{
831
+ Context: context, Modulo: modulo, Type: typ, Name: name,
832
+ }))
833
+ default:
834
+ return nestiaCoreValidatorObject("type", "validate", nativeprogrammers.ValidateProgrammer.Write(nativeprogrammers.ValidateProgrammer_IProps{
835
+ Context: context, Modulo: modulo, Type: typ, Name: name,
836
+ Config: nativeprogrammers.ValidateProgrammer_IConfig{Equals: false},
837
+ }))
838
+ }
839
+ }
840
+
841
+ // nestiaCoreGenerateTypedHeaders intentionally collapses the 10-mode validate
842
+ // option down to {assert, is, validate}. Header values are strings keyed by
843
+ // name; deep-clone and prune semantics that @TypedBody honors (assertClone,
844
+ // assertPrune, validateClone, validatePrune, etc.) have no meaningful effect
845
+ // on a flat string→string map. Pass-through to the base programmer is the
846
+ // intended behavior, not a fallthrough — matches v6 parity. See also
847
+ // nestiaCoreGenerateTypedQuery and nestiaCoreGenerateTypedFormDataBody.
848
+ func nestiaCoreGenerateTypedHeaders(prog *driver.Program, options nestiaCoreOptions, modulo *shimast.Node, typ *shimchecker.Type) *shimast.Node {
849
+ context := nestiaCoreTypiaContext(prog, false, false, false)
850
+ name := nestiaCoreTypeName(prog, typ)
851
+ category := options.Validate
852
+ if category == "is" || category == "equals" {
853
+ return nestiaCoreValidatorObject("type", "is", nativehttp.HttpIsHeadersProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name}))
854
+ }
855
+ if strings.HasPrefix(category, "validate") {
856
+ return nestiaCoreValidatorObject("type", "validate", nativehttp.HttpValidateHeadersProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name}))
857
+ }
858
+ return nestiaCoreValidatorObject("type", "assert", nativehttp.HttpAssertHeadersProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name}))
859
+ }
860
+
861
+ func nestiaCoreGenerateTypedParam(prog *driver.Program, modulo *shimast.Node, typ *shimchecker.Type) *shimast.Node {
862
+ return nativehttp.HttpParameterProgrammer.Write(nativecontext.IProgrammerProps{
863
+ Context: nestiaCoreTypiaContext(prog, true, false, false),
864
+ Modulo: modulo,
865
+ Type: typ,
866
+ Name: nestiaCoreTypeName(prog, typ),
867
+ })
868
+ }
869
+
870
+ func nestiaCoreGenerateTypedQuery(prog *driver.Program, options nestiaCoreOptions, modulo *shimast.Node, typ *shimchecker.Type, allowOptional bool) *shimast.Node {
871
+ nestiaCoreValidateTypedQuery(prog, options, typ, allowOptional, "@nestia.core.TypedQuery")
872
+ context := nestiaCoreTypiaContext(prog, false, false, false)
873
+ name := nestiaCoreTypeName(prog, typ)
874
+ category := options.Validate
875
+ if category == "is" || category == "equals" {
876
+ return nestiaCoreValidatorObject("type", "is", nativehttp.HttpIsQueryProgrammer.Write(nativehttp.HttpIsQueryProgrammer_IProps{Context: context, Modulo: modulo, Type: typ, Name: name, AllowOptional: allowOptional}))
877
+ }
878
+ if category == "validate" || category == "validateEquals" || category == "validateClone" || category == "validatePrune" {
879
+ return nestiaCoreValidatorObject("type", "validate", nativehttp.HttpValidateQueryProgrammer.Write(nativehttp.HttpValidateQueryProgrammer_IProps{Context: context, Modulo: modulo, Type: typ, Name: name, AllowOptional: allowOptional}))
880
+ }
881
+ return nestiaCoreValidatorObject("type", "assert", nativehttp.HttpAssertQueryProgrammer.Write(nativehttp.HttpAssertQueryProgrammer_IProps{Context: context, Modulo: modulo, Type: typ, Name: name, AllowOptional: allowOptional}))
882
+ }
883
+
884
+ func nestiaCoreGenerateTypedFormDataBody(prog *driver.Program, options nestiaCoreOptions, modulo *shimast.Node, typ *shimchecker.Type) *shimast.Node {
885
+ context := nestiaCoreTypiaContext(prog, false, false, false)
886
+ name := nestiaCoreTypeName(prog, typ)
887
+ category := options.Validate
888
+ files := nestiaCoreFormDataFiles(prog, typ)
889
+ validator := nativehttp.HttpAssertFormDataProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name})
890
+ key := "assert"
891
+ if category == "is" || category == "equals" {
892
+ key = "is"
893
+ validator = nativehttp.HttpIsFormDataProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name})
894
+ } else if category == "validate" || category == "validateEquals" || category == "validateClone" || category == "validatePrune" {
895
+ key = "validate"
896
+ validator = nativehttp.HttpValidateFormDataProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name})
897
+ }
898
+ return nestiaCoreFactory.NewObjectLiteralExpression(nestiaCoreFactory.NewNodeList([]*shimast.Node{
899
+ nestiaCoreProperty("files", nestiaCoreFormDataFilesExpression(files)),
900
+ nestiaCoreProperty("validator", nestiaCoreValidatorObject("type", key, validator)),
901
+ }), true)
902
+ }
903
+
904
+ type nestiaCoreFormDataFile struct {
905
+ Name string
906
+ Limit *int
907
+ }
908
+
909
+ func nestiaCoreFormDataFiles(prog *driver.Program, typ *shimchecker.Type) []nestiaCoreFormDataFile {
910
+ collection := schemametadata.NewMetadataCollection()
911
+ result := nativefactories.MetadataFactory.Analyze(nativefactories.MetadataFactory_IProps{
912
+ Checker: prog.Checker,
913
+ Options: nativefactories.MetadataFactory_IOptions{
914
+ Escape: false,
915
+ Constant: true,
916
+ Absorb: true,
917
+ Validate: nativehttp.HttpFormDataProgrammer.Validate,
918
+ },
919
+ Components: collection,
920
+ Type: typ,
921
+ })
922
+ if result.Success == false {
923
+ panic(fmt.Errorf("failed to analyze form-data metadata: %d error(s)", len(result.Errors)))
924
+ }
925
+ files := []nestiaCoreFormDataFile{}
926
+ if result.Data == nil || len(result.Data.Objects) == 0 || result.Data.Objects[0] == nil || result.Data.Objects[0].Type == nil {
927
+ return files
928
+ }
929
+ for _, property := range result.Data.Objects[0].Type.Properties {
930
+ if property == nil || property.Value == nil {
931
+ continue
932
+ }
933
+ direct := nestiaCoreMetadataHasFile(property.Value)
934
+ array := nestiaCoreMetadataArrayHasFile(property.Value)
935
+ if direct == false && array == false {
936
+ continue
937
+ }
938
+ name, ok := nestiaCorePropertyStringKey(property)
939
+ if ok == false {
940
+ continue
941
+ }
942
+ var limit *int
943
+ if direct {
944
+ one := 1
945
+ limit = &one
946
+ }
947
+ files = append(files, nestiaCoreFormDataFile{
948
+ Name: name,
949
+ Limit: limit,
950
+ })
951
+ }
952
+ return files
953
+ }
954
+
955
+ func nestiaCoreMetadataHasFile(metadata *schemametadata.MetadataSchema) bool {
956
+ if metadata == nil {
957
+ return false
958
+ }
959
+ for _, native := range metadata.Natives {
960
+ if native != nil && (native.Name == "File" || native.Name == "Blob") {
961
+ return true
962
+ }
963
+ }
964
+ return false
965
+ }
966
+
967
+ func nestiaCoreMetadataArrayHasFile(metadata *schemametadata.MetadataSchema) bool {
968
+ if metadata == nil {
969
+ return false
970
+ }
971
+ for _, array := range metadata.Arrays {
972
+ if array == nil || array.Type == nil || array.Type.Value == nil {
973
+ continue
974
+ }
975
+ if nestiaCoreMetadataHasFile(array.Type.Value) {
976
+ return true
977
+ }
978
+ }
979
+ return false
980
+ }
981
+
982
+ func nestiaCorePropertyStringKey(property *schemametadata.MetadataProperty) (string, bool) {
983
+ if property == nil || property.Key == nil {
984
+ return "", false
985
+ }
986
+ for _, constant := range property.Key.Constants {
987
+ if constant == nil || constant.Type != "string" {
988
+ continue
989
+ }
990
+ for _, value := range constant.Values {
991
+ if value == nil {
992
+ continue
993
+ }
994
+ name, ok := value.Value.(string)
995
+ if ok {
996
+ return name, true
997
+ }
998
+ }
999
+ }
1000
+ return "", false
1001
+ }
1002
+
1003
+ func nestiaCoreFormDataFilesExpression(files []nestiaCoreFormDataFile) *shimast.Node {
1004
+ elements := make([]*shimast.Node, 0, len(files))
1005
+ for _, file := range files {
1006
+ limit := nestiaCoreFactory.NewKeywordExpression(shimast.KindNullKeyword)
1007
+ if file.Limit != nil {
1008
+ limit = nativefactories.LiteralFactory.Write(*file.Limit)
1009
+ }
1010
+ elements = append(elements, nestiaCoreFactory.NewObjectLiteralExpression(nestiaCoreFactory.NewNodeList([]*shimast.Node{
1011
+ nestiaCoreProperty("name", nativefactories.LiteralFactory.Write(file.Name)),
1012
+ nestiaCoreProperty("limit", limit),
1013
+ }), true))
1014
+ }
1015
+ return nestiaCoreFactory.NewArrayLiteralExpression(nestiaCoreFactory.NewNodeList(elements), true)
1016
+ }
1017
+
1018
+ func nestiaCoreGeneratePlainBody(prog *driver.Program, modulo *shimast.Node, typ *shimchecker.Type) *shimast.Node {
1019
+ nestiaCoreValidatePlainBody(prog, typ)
1020
+ return nativeprogrammers.AssertProgrammer.Write(nativeprogrammers.AssertProgrammer_IProps{
1021
+ Context: nestiaCoreTypiaContext(prog, false, false, false),
1022
+ Modulo: modulo,
1023
+ Type: typ,
1024
+ Name: nestiaCoreTypeName(prog, typ),
1025
+ Config: nativeprogrammers.AssertProgrammer_IConfig{Equals: false, Guard: false},
1026
+ })
1027
+ }
1028
+
1029
+ func nestiaCoreGenerateTypedRoute(prog *driver.Program, options nestiaCoreOptions, modulo *shimast.Node, typ *shimchecker.Type) *shimast.Node {
1030
+ nestiaCoreValidateTypedRoute(prog, options, typ)
1031
+ if options.StringifyNull {
1032
+ return nestiaCoreFactory.NewKeywordExpression(shimast.KindNullKeyword)
1033
+ }
1034
+ context := nestiaCoreTypiaContext(prog, false, false, false)
1035
+ name := nestiaCoreTypeName(prog, typ)
1036
+ switch options.Stringify {
1037
+ case "is":
1038
+ return nestiaCoreValidatorObject("type", "is", nativejson.JsonIsStringifyProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name}))
1039
+ case "validate":
1040
+ return nestiaCoreValidatorObject("type", "validate", nativejson.JsonValidateStringifyProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name}))
1041
+ case "stringify":
1042
+ return nestiaCoreValidatorObject("type", "stringify", nativejson.JsonStringifyProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name}))
1043
+ case "validate.log":
1044
+ return nestiaCoreValidatorObjectWithKey("type", "validate.log", "validate", nativejson.JsonValidateStringifyProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name}))
1045
+ default:
1046
+ return nestiaCoreValidatorObject("type", "assert", nativejson.JsonAssertStringifyProgrammer.Write(nativecontext.IProgrammerProps{Context: context, Modulo: modulo, Type: typ, Name: name}))
1047
+ }
1048
+ }
1049
+
1050
+ func nestiaCoreGenerateTypedQueryRoute(prog *driver.Program, options nestiaCoreOptions, modulo *shimast.Node, typ *shimchecker.Type) *shimast.Node {
1051
+ nestiaCoreValidateTypedQueryRoute(prog, options, typ)
1052
+ if options.StringifyNull {
1053
+ return nestiaCoreFactory.NewKeywordExpression(shimast.KindNullKeyword)
1054
+ }
1055
+ switch options.Stringify {
1056
+ case "is":
1057
+ return nestiaCoreValidatorObject("type", "is", nestiaCoreHttpIsQuerifyProgrammer(prog, modulo, typ))
1058
+ case "validate":
1059
+ return nestiaCoreValidatorObject("type", "validate", nestiaCoreHttpValidateQuerifyProgrammer(prog, modulo, typ))
1060
+ case "stringify":
1061
+ return nestiaCoreValidatorObject("type", "stringify", nestiaCoreHttpQuerifyProgrammer(prog, typ))
1062
+ default:
1063
+ return nestiaCoreValidatorObject("type", "assert", nestiaCoreHttpAssertQuerifyProgrammer(prog, modulo, typ))
1064
+ }
1065
+ }
1066
+
1067
+ func nestiaCoreTypiaContext(prog *driver.Program, numeric bool, finite bool, functional bool) nativecontext.ITypiaContext {
1068
+ return nativecontext.ITypiaContext{
1069
+ Program: prog,
1070
+ CompilerOptions: prog.ParsedConfig.ParsedConfig.CompilerOptions,
1071
+ Checker: prog.Checker,
1072
+ Options: nativecontext.ITransformOptions{
1073
+ Numeric: &numeric,
1074
+ Finite: &finite,
1075
+ Functional: &functional,
1076
+ Runtime: "typia",
1077
+ },
1078
+ Importer: nativeprogrammers.NewImportProgrammer(nativeprogrammers.ImportProgrammer_IOptions{
1079
+ InternalPrefix: "typia_transform_",
1080
+ Runtime: "typia",
1081
+ }),
1082
+ }
1083
+ }
1084
+
1085
+ func nestiaCoreStrictMode(prog *driver.Program) bool {
1086
+ if prog == nil || prog.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig == nil || prog.ParsedConfig.ParsedConfig.CompilerOptions == nil {
1087
+ return true
1088
+ }
1089
+ options := prog.ParsedConfig.ParsedConfig.CompilerOptions
1090
+ return options.GetStrictOptionValue(options.StrictNullChecks)
1091
+ }
1092
+
1093
+ func nestiaCoreLlmConfig(options nestiaCoreOptions) map[string]any {
1094
+ return map[string]any{"strict": options.LlmStrict}
1095
+ }
1096
+
1097
+ func nestiaCoreValidateTypedBody(prog *driver.Program, options nestiaCoreOptions, typ *shimchecker.Type) {
1098
+ var validate nativefactories.MetadataFactory_Validator
1099
+ if options.Llm {
1100
+ validate = func(next struct {
1101
+ Metadata *schemametadata.MetadataSchema
1102
+ Explore nativefactories.MetadataFactory_IExplore
1103
+ Top *schemametadata.MetadataSchema
1104
+ }) []string {
1105
+ return nativellm.LlmSchemaProgrammer.Validate(struct {
1106
+ Config map[string]any
1107
+ Metadata *schemametadata.MetadataSchema
1108
+ Explore nativefactories.MetadataFactory_IExplore
1109
+ }{
1110
+ Config: nestiaCoreLlmConfig(options),
1111
+ Metadata: next.Metadata,
1112
+ Explore: next.Explore,
1113
+ })
1114
+ }
1115
+ }
1116
+ nativefactories.JsonMetadataFactory.Analyze(nativefactories.JsonMetadataFactory_IProps{
1117
+ Method: "@nestia.core.TypedBody",
1118
+ Checker: prog.Checker,
1119
+ Type: typ,
1120
+ Validate: validate,
1121
+ })
1122
+ }
1123
+
1124
+ func nestiaCoreValidateTypedRoute(prog *driver.Program, options nestiaCoreOptions, typ *shimchecker.Type) {
1125
+ if options.Llm == false {
1126
+ return
1127
+ }
1128
+ nativefactories.JsonMetadataFactory.Analyze(nativefactories.JsonMetadataFactory_IProps{
1129
+ Method: "@nestia.core.TypedRoute",
1130
+ Checker: prog.Checker,
1131
+ Type: typ,
1132
+ Validate: func(next struct {
1133
+ Metadata *schemametadata.MetadataSchema
1134
+ Explore nativefactories.MetadataFactory_IExplore
1135
+ Top *schemametadata.MetadataSchema
1136
+ }) []string {
1137
+ if next.Metadata == nil || next.Metadata.Size() == 0 {
1138
+ return nil
1139
+ }
1140
+ return nativellm.LlmParametersProgrammer.Validate(struct {
1141
+ Config map[string]any
1142
+ Metadata *schemametadata.MetadataSchema
1143
+ Explore nativefactories.MetadataFactory_IExplore
1144
+ }{
1145
+ Config: nestiaCoreLlmConfig(options),
1146
+ Metadata: next.Metadata,
1147
+ Explore: next.Explore,
1148
+ })
1149
+ },
1150
+ })
1151
+ }
1152
+
1153
+ func nestiaCoreValidateTypedQuery(prog *driver.Program, options nestiaCoreOptions, typ *shimchecker.Type, allowOptional bool, code string) {
1154
+ if options.Llm == false {
1155
+ return
1156
+ }
1157
+ collection := schemametadata.NewMetadataCollection()
1158
+ result := nativefactories.MetadataFactory.Analyze(nativefactories.MetadataFactory_IProps{
1159
+ Checker: prog.Checker,
1160
+ Options: nativefactories.MetadataFactory_IOptions{
1161
+ Escape: false,
1162
+ Constant: true,
1163
+ Absorb: true,
1164
+ Validate: func(next struct {
1165
+ Metadata *schemametadata.MetadataSchema
1166
+ Explore nativefactories.MetadataFactory_IExplore
1167
+ Top *schemametadata.MetadataSchema
1168
+ }) []string {
1169
+ errors := nativehttp.HttpQueryProgrammer.Validate(struct {
1170
+ Metadata *schemametadata.MetadataSchema
1171
+ Explore nativefactories.MetadataFactory_IExplore
1172
+ Top *schemametadata.MetadataSchema
1173
+ AllowOptional bool
1174
+ }{
1175
+ Metadata: next.Metadata,
1176
+ Explore: next.Explore,
1177
+ Top: next.Top,
1178
+ AllowOptional: allowOptional,
1179
+ })
1180
+ errors = append(errors, nativellm.LlmSchemaProgrammer.Validate(struct {
1181
+ Config map[string]any
1182
+ Metadata *schemametadata.MetadataSchema
1183
+ Explore nativefactories.MetadataFactory_IExplore
1184
+ }{
1185
+ Config: nestiaCoreLlmConfig(options),
1186
+ Metadata: next.Metadata,
1187
+ Explore: next.Explore,
1188
+ })...)
1189
+ return errors
1190
+ },
1191
+ },
1192
+ Components: collection,
1193
+ Type: typ,
1194
+ })
1195
+ if result.Success == false {
1196
+ panic(nativecontext.TransformerError_from(struct {
1197
+ Code string
1198
+ Errors []nativecontext.TransformerError_MetadataFactory_IError
1199
+ }{
1200
+ Code: code,
1201
+ Errors: nestiaCoreMetadataErrors(result.Errors),
1202
+ }))
1203
+ }
1204
+ }
1205
+
1206
+ func nestiaCoreValidateTypedQueryRoute(prog *driver.Program, options nestiaCoreOptions, typ *shimchecker.Type) {
1207
+ if options.Llm == false {
1208
+ return
1209
+ }
1210
+ collection := schemametadata.NewMetadataCollection()
1211
+ result := nativefactories.MetadataFactory.Analyze(nativefactories.MetadataFactory_IProps{
1212
+ Checker: prog.Checker,
1213
+ Options: nativefactories.MetadataFactory_IOptions{
1214
+ Escape: false,
1215
+ Constant: true,
1216
+ Absorb: true,
1217
+ Validate: func(next struct {
1218
+ Metadata *schemametadata.MetadataSchema
1219
+ Explore nativefactories.MetadataFactory_IExplore
1220
+ Top *schemametadata.MetadataSchema
1221
+ }) []string {
1222
+ errors := nativehttp.HttpQueryProgrammer.Validate(struct {
1223
+ Metadata *schemametadata.MetadataSchema
1224
+ Explore nativefactories.MetadataFactory_IExplore
1225
+ Top *schemametadata.MetadataSchema
1226
+ AllowOptional bool
1227
+ }{
1228
+ Metadata: next.Metadata,
1229
+ Explore: next.Explore,
1230
+ Top: next.Top,
1231
+ AllowOptional: true,
1232
+ })
1233
+ if next.Metadata != nil && next.Metadata.Size() != 0 {
1234
+ errors = append(errors, nativellm.LlmParametersProgrammer.Validate(struct {
1235
+ Config map[string]any
1236
+ Metadata *schemametadata.MetadataSchema
1237
+ Explore nativefactories.MetadataFactory_IExplore
1238
+ }{
1239
+ Config: nestiaCoreLlmConfig(options),
1240
+ Metadata: next.Metadata,
1241
+ Explore: next.Explore,
1242
+ })...)
1243
+ }
1244
+ return errors
1245
+ },
1246
+ },
1247
+ Components: collection,
1248
+ Type: typ,
1249
+ })
1250
+ if result.Success == false {
1251
+ panic(nativecontext.TransformerError_from(struct {
1252
+ Code string
1253
+ Errors []nativecontext.TransformerError_MetadataFactory_IError
1254
+ }{
1255
+ Code: "@nestia.core.TypedQueryRoute",
1256
+ Errors: nestiaCoreMetadataErrors(result.Errors),
1257
+ }))
1258
+ }
1259
+ }
1260
+
1261
+ func nestiaCoreValidatePlainBody(prog *driver.Program, typ *shimchecker.Type) {
1262
+ collection := schemametadata.NewMetadataCollection()
1263
+ result := nativefactories.MetadataFactory.Analyze(nativefactories.MetadataFactory_IProps{
1264
+ Checker: prog.Checker,
1265
+ Options: nativefactories.MetadataFactory_IOptions{
1266
+ Escape: false,
1267
+ Constant: true,
1268
+ Absorb: true,
1269
+ Validate: func(next struct {
1270
+ Metadata *schemametadata.MetadataSchema
1271
+ Explore nativefactories.MetadataFactory_IExplore
1272
+ Top *schemametadata.MetadataSchema
1273
+ }) []string {
1274
+ return nestiaCoreValidatePlainBodyMetadata(next.Metadata)
1275
+ },
1276
+ },
1277
+ Components: collection,
1278
+ Type: typ,
1279
+ })
1280
+ if result.Success == false {
1281
+ panic(nativecontext.TransformerError_from(struct {
1282
+ Code string
1283
+ Errors []nativecontext.TransformerError_MetadataFactory_IError
1284
+ }{
1285
+ Code: "nestia.core.PlainBody",
1286
+ Errors: nestiaCoreMetadataErrors(result.Errors),
1287
+ }))
1288
+ }
1289
+ }
1290
+
1291
+ func nestiaCoreValidatePlainBodyMetadata(metadata *schemametadata.MetadataSchema) []string {
1292
+ if metadata == nil {
1293
+ return nil
1294
+ }
1295
+ errors := []string{}
1296
+ expected := 0
1297
+ for _, atomic := range metadata.Atomics {
1298
+ if atomic != nil && atomic.Type == "string" {
1299
+ expected = 1
1300
+ break
1301
+ }
1302
+ }
1303
+ expected += len(metadata.Templates)
1304
+ for _, constant := range metadata.Constants {
1305
+ if constant != nil && constant.Type == "string" {
1306
+ expected += len(constant.Values)
1307
+ }
1308
+ }
1309
+ if expected == 0 || expected != metadata.Size() {
1310
+ errors = append(errors, "only string type is allowed")
1311
+ }
1312
+ if metadata.Nullable {
1313
+ errors = append(errors, "do not allow nullable type")
1314
+ } else if metadata.Any {
1315
+ errors = append(errors, "do not allow any type")
1316
+ }
1317
+ return errors
1318
+ }
1319
+
1320
+ func nestiaCoreMetadataErrors(errors []nativefactories.MetadataFactory_IError) []nativecontext.TransformerError_MetadataFactory_IError {
1321
+ output := make([]nativecontext.TransformerError_MetadataFactory_IError, 0, len(errors))
1322
+ for _, err := range errors {
1323
+ output = append(output, nativecontext.TransformerError_MetadataFactory_IError{
1324
+ Name: err.Name,
1325
+ Explore: nativecontext.TransformerError_MetadataFactory_IExplore{
1326
+ Object: err.Explore.Object,
1327
+ Property: err.Explore.Property,
1328
+ Parameter: err.Explore.Parameter,
1329
+ Output: err.Explore.Output,
1330
+ },
1331
+ Messages: err.Messages,
1332
+ })
1333
+ }
1334
+ return output
1335
+ }
1336
+
1337
+ func nestiaCoreValidatorObject(typeKey string, key string, validator *shimast.Node) *shimast.Node {
1338
+ return nestiaCoreValidatorObjectWithKey(typeKey, key, key, validator)
1339
+ }
1340
+
1341
+ func nestiaCoreValidatorObjectWithKey(typeKey string, typeValue string, validatorKey string, validator *shimast.Node) *shimast.Node {
1342
+ return nestiaCoreFactory.NewObjectLiteralExpression(nestiaCoreFactory.NewNodeList([]*shimast.Node{
1343
+ nestiaCoreProperty(typeKey, nestiaCoreFactory.NewStringLiteral(typeValue, shimast.TokenFlagsNone)),
1344
+ nestiaCoreProperty(validatorKey, validator),
1345
+ }), true)
1346
+ }
1347
+
1348
+ func nestiaCoreProperty(name string, initializer *shimast.Node) *shimast.Node {
1349
+ return nestiaCoreFactory.NewPropertyAssignment(
1350
+ nil,
1351
+ nativefactories.IdentifierFactory.Identifier(name),
1352
+ nil,
1353
+ nil,
1354
+ initializer,
1355
+ )
1356
+ }
1357
+
1358
+ func safeNestiaCoreGenerate(
1359
+ generator func() (*shimast.Node, error),
1360
+ prog *driver.Program,
1361
+ file *shimast.SourceFile,
1362
+ preserveTypes bool,
1363
+ ) (text string, err error) {
1364
+ defer func() {
1365
+ if exp := recover(); exp != nil {
1366
+ if os.Getenv("NESTIA_NATIVE_DEBUG_STACK") != "" {
1367
+ err = fmt.Errorf("%v\n%s", exp, debug.Stack())
1368
+ } else {
1369
+ err = fmt.Errorf("%v", exp)
1370
+ }
1371
+ }
1372
+ }()
1373
+ node, err := generator()
1374
+ if err != nil {
1375
+ return "", err
1376
+ }
1377
+ return emitNestiaCoreExpression(prog, file, node, preserveTypes), nil
1378
+ }
1379
+
1380
+ func emitNestiaCoreExpression(prog *driver.Program, file *shimast.SourceFile, node *shimast.Node, preserveTypes bool) string {
1381
+ var text string
1382
+ if preserveTypes {
1383
+ text = emitNestiaPreservingTypesWithIdentifierSubstitutions(node, file, nil)
1384
+ } else {
1385
+ text = emitNestiaWithIdentifierSubstitutions(
1386
+ node,
1387
+ file,
1388
+ identifierSubstitutionsForEmit(prog, file),
1389
+ )
1390
+ }
1391
+ return cleanupNestiaCorePrintedExpression(text)
1392
+ }
1393
+
1394
+ func cleanupNestiaCorePrintedExpression(text string) string {
1395
+ text = strings.TrimSpace(text)
1396
+ text = strings.TrimSuffix(text, ";")
1397
+ text = nestiaCoreSingleParameterArrowPattern.ReplaceAllString(text, `${1}(${2}) =>`)
1398
+ if strings.HasPrefix(text, "(") && strings.HasSuffix(text, ")") {
1399
+ return text
1400
+ }
1401
+ if strings.Contains(text, "=>") || strings.Contains(text, "function") {
1402
+ return "(" + text + ")"
1403
+ }
1404
+ return text
1405
+ }
1406
+
1407
+ var nestiaCoreSingleParameterArrowPattern = regexp.MustCompile(`(^|[\s(=,:?])([A-Za-z_$][A-Za-z0-9_$]*) =>`)
1408
+
1409
+ func nestiaCoreMethodReturnType(prog *driver.Program, node *shimast.Node) *shimchecker.Type {
1410
+ signature := prog.Checker.GetSignatureFromDeclaration(node)
1411
+ if signature == nil {
1412
+ return nil
1413
+ }
1414
+ typ := prog.Checker.GetReturnTypeOfSignature(signature)
1415
+ if typ == nil {
1416
+ return nil
1417
+ }
1418
+ symbol := typ.Symbol()
1419
+ if symbol != nil && symbol.Name == "Promise" {
1420
+ args := prog.Checker.GetTypeArguments(typ)
1421
+ if len(args) == 1 {
1422
+ return args[0]
1423
+ }
1424
+ }
1425
+ return typ
1426
+ }
1427
+
1428
+ func nestiaCoreShouldSkipMethodDecorator(prog *driver.Program, call *shimast.CallExpression) bool {
1429
+ count := nestiaCoreArgumentCount(call)
1430
+ if count >= 2 {
1431
+ return true
1432
+ }
1433
+ if count == 1 {
1434
+ last := call.Arguments.Nodes[0]
1435
+ if last.Kind == shimast.KindObjectLiteralExpression {
1436
+ return true
1437
+ }
1438
+ if nestiaCoreHasPathLiteralArgument(call) {
1439
+ return false
1440
+ }
1441
+ typ := prog.Checker.GetTypeAtLocation(last)
1442
+ if typ != nil && typ.Flags()&shimchecker.TypeFlagsObject != 0 &&
1443
+ shimchecker.IsTupleType(typ) == false &&
1444
+ shimchecker.Checker_isArrayType(prog.Checker, typ) == false {
1445
+ return true
1446
+ }
1447
+ }
1448
+ return false
1449
+ }
1450
+
1451
+ func nestiaCoreHasPathLiteralArgument(call *shimast.CallExpression) bool {
1452
+ source, ok := sourceFileText(shimast.GetSourceFileOfNode(call.AsNode()))
1453
+ if !ok {
1454
+ return false
1455
+ }
1456
+ open, close, ok := callArgumentBounds(source, call)
1457
+ if !ok {
1458
+ return false
1459
+ }
1460
+ text := strings.TrimSpace(source[open+1 : close])
1461
+ return strings.HasPrefix(text, `"`) ||
1462
+ strings.HasPrefix(text, `'`) ||
1463
+ strings.HasPrefix(text, "`") ||
1464
+ strings.HasPrefix(text, "[")
1465
+ }
1466
+
1467
+ func nestiaCoreArgumentCount(call *shimast.CallExpression) int {
1468
+ if call == nil || call.Arguments == nil {
1469
+ return 0
1470
+ }
1471
+ return len(call.Arguments.Nodes)
1472
+ }
1473
+
1474
+ func callArgumentBounds(source string, call *shimast.CallExpression) (int, int, bool) {
1475
+ if call == nil || call.AsNode() == nil || call.Expression == nil {
1476
+ return 0, 0, false
1477
+ }
1478
+ start := call.Expression.End()
1479
+ end := call.AsNode().End()
1480
+ if start < 0 || end > len(source) || start >= end {
1481
+ start = call.AsNode().Pos()
1482
+ }
1483
+ open := strings.IndexByte(source[start:end], '(')
1484
+ if open < 0 {
1485
+ return 0, 0, false
1486
+ }
1487
+ open += start
1488
+ close, ok := matchNativeParen(source, open)
1489
+ return open, close, ok
1490
+ }
1491
+
1492
+ func appendArgumentsText(current string, arguments []string) string {
1493
+ current = strings.TrimSpace(current)
1494
+ next := strings.Join(arguments, ", ")
1495
+ if current == "" {
1496
+ return next
1497
+ }
1498
+ return current + ", " + next
1499
+ }
1500
+
1501
+ func nestiaCoreExpressionSegments(node *shimast.Node) []string {
1502
+ if node == nil {
1503
+ return nil
1504
+ }
1505
+ if node.Kind == shimast.KindIdentifier {
1506
+ if id := node.AsIdentifier(); id != nil {
1507
+ return []string{id.Text}
1508
+ }
1509
+ }
1510
+ if node.Kind == shimast.KindPropertyAccessExpression {
1511
+ access := node.AsPropertyAccessExpression()
1512
+ if access == nil {
1513
+ return nil
1514
+ }
1515
+ left := nestiaCoreExpressionSegments(access.Expression)
1516
+ name := access.Name()
1517
+ if len(left) == 0 || name == nil || name.Kind != shimast.KindIdentifier {
1518
+ return nil
1519
+ }
1520
+ return append(left, name.AsIdentifier().Text)
1521
+ }
1522
+ return nil
1523
+ }
1524
+
1525
+ func nestiaCoreModuloNode(node *shimast.Node) *shimast.Node {
1526
+ segments := nestiaCoreExpressionSegments(node)
1527
+ if len(segments) == 0 {
1528
+ return nestiaCoreFactory.NewIdentifier("nestia_core_transform")
1529
+ }
1530
+ return nestiaCoreFactory.NewIdentifier(strings.Join(segments, "_"))
1531
+ }
1532
+
1533
+ func nestiaCoreTypeName(prog *driver.Program, typ *shimchecker.Type) *string {
1534
+ name := nestiaCoreTypeNameText(prog, typ)
1535
+ return &name
1536
+ }
1537
+
1538
+ type nestiaCoreTypeNameCacheKey struct {
1539
+ checker *shimchecker.Checker
1540
+ typ *shimchecker.Type
1541
+ }
1542
+
1543
+ var nestiaCoreTypeNameCache sync.Map
1544
+
1545
+ func nestiaCoreTypeNameText(prog *driver.Program, typ *shimchecker.Type) string {
1546
+ if prog != nil && prog.Checker != nil && typ != nil {
1547
+ key := nestiaCoreTypeNameCacheKey{checker: prog.Checker, typ: typ}
1548
+ if cached, ok := nestiaCoreTypeNameCache.Load(key); ok {
1549
+ return cached.(string)
1550
+ }
1551
+ name := prog.Checker.TypeToString(typ)
1552
+ nestiaCoreTypeNameCache.Store(key, name)
1553
+ return name
1554
+ }
1555
+ return "any"
1556
+ }
1557
+
1558
+ func nestiaCoreSegmentsHaveSuffix(segments []string, suffix []string) bool {
1559
+ if len(suffix) > len(segments) {
1560
+ return false
1561
+ }
1562
+ offset := len(segments) - len(suffix)
1563
+ for i, part := range suffix {
1564
+ if segments[offset+i] != part {
1565
+ return false
1566
+ }
1567
+ }
1568
+ return true
1569
+ }
1570
+
1571
+ func nestiaCoreTargetCandidates(prog *driver.Program, site nestiaCoreSite) []string {
1572
+ if len(site.Segments) == 0 {
1573
+ return nil
1574
+ }
1575
+ candidates := []string{strings.Join(site.Segments, ".")}
1576
+ substitutions := identifierSubstitutionsForEmit(prog, site.File)
1577
+ if substitutions != nil {
1578
+ if mapped, ok := substitutions[site.Segments[0]]; ok {
1579
+ parts := append([]string{mapped}, site.Segments[1:]...)
1580
+ candidates = append(candidates, strings.Join(parts, "."))
1581
+ }
1582
+ }
1583
+ sort.SliceStable(candidates, func(i, j int) bool {
1584
+ return len(candidates[i]) > len(candidates[j])
1585
+ })
1586
+ return candidates
1587
+ }
1588
+
1589
+ func identifierSubstitutionsForEmit(program *driver.Program, file any) map[string]string {
1590
+ if program == nil {
1591
+ return nil
1592
+ }
1593
+ sourceFile, ok := file.(*shimast.SourceFile)
1594
+ if ok == false {
1595
+ return nil
1596
+ }
1597
+ return commonJSImportIdentifierSubstitutions(sourceFile)
1598
+ }
1599
+
1600
+ type commonJSImportIdentifierSubstitutionsCacheEntry struct {
1601
+ value map[string]string
1602
+ }
1603
+
1604
+ var commonJSImportIdentifierSubstitutionsCache sync.Map
1605
+
1606
+ func commonJSImportIdentifierSubstitutions(file *shimast.SourceFile) map[string]string {
1607
+ if file == nil || file.Statements == nil {
1608
+ return nil
1609
+ }
1610
+ if cached, ok := commonJSImportIdentifierSubstitutionsCache.Load(file); ok {
1611
+ return cached.(commonJSImportIdentifierSubstitutionsCacheEntry).value
1612
+ }
1613
+ output := map[string]string{}
1614
+ counts := map[string]int{}
1615
+ for _, stmt := range file.Statements.Nodes {
1616
+ if stmt == nil || stmt.Kind != shimast.KindImportDeclaration {
1617
+ continue
1618
+ }
1619
+ decl := stmt.AsImportDeclaration()
1620
+ if decl == nil || decl.ImportClause == nil || decl.ModuleSpecifier == nil || decl.ModuleSpecifier.Kind != shimast.KindStringLiteral {
1621
+ continue
1622
+ }
1623
+ clause := decl.ImportClause.AsImportClause()
1624
+ if clause == nil || clause.PhaseModifier == shimast.KindTypeKeyword {
1625
+ continue
1626
+ }
1627
+ base := commonJSImportAliasBase(decl.ModuleSpecifier.Text())
1628
+ counts[base]++
1629
+ moduleAlias := base + "_" + strconv.Itoa(counts[base])
1630
+ if name := clause.Name(); name != nil {
1631
+ output[name.Text()] = moduleAlias + ".default"
1632
+ }
1633
+ if clause.NamedBindings == nil || clause.NamedBindings.Kind != shimast.KindNamedImports {
1634
+ continue
1635
+ }
1636
+ named := clause.NamedBindings.AsNamedImports()
1637
+ if named == nil || named.Elements == nil {
1638
+ continue
1639
+ }
1640
+ for _, elem := range named.Elements.Nodes {
1641
+ if elem == nil {
1642
+ continue
1643
+ }
1644
+ spec := elem.AsImportSpecifier()
1645
+ if spec == nil || spec.IsTypeOnly {
1646
+ continue
1647
+ }
1648
+ name := spec.Name()
1649
+ if name == nil {
1650
+ continue
1651
+ }
1652
+ local := name.Text()
1653
+ imported := local
1654
+ if spec.PropertyName != nil {
1655
+ imported = spec.PropertyName.Text()
1656
+ }
1657
+ output[local] = moduleAlias + "." + imported
1658
+ }
1659
+ }
1660
+ if len(output) == 0 {
1661
+ output = nil
1662
+ }
1663
+ commonJSImportIdentifierSubstitutionsCache.Store(file, commonJSImportIdentifierSubstitutionsCacheEntry{value: output})
1664
+ return output
1665
+ }
1666
+
1667
+ func commonJSImportAliasBase(module string) string {
1668
+ base := strings.TrimSuffix(filepath.Base(module), filepath.Ext(module))
1669
+ if base == "" || base == "." || base == string(filepath.Separator) {
1670
+ base = "mod"
1671
+ }
1672
+ var builder strings.Builder
1673
+ for _, r := range base {
1674
+ if r == '_' || r == '$' || unicode.IsLetter(r) || unicode.IsDigit(r) {
1675
+ builder.WriteRune(r)
1676
+ } else {
1677
+ builder.WriteByte('_')
1678
+ }
1679
+ }
1680
+ text := builder.String()
1681
+ if text == "" {
1682
+ text = "mod"
1683
+ }
1684
+ first := []rune(text)[0]
1685
+ if first != '_' && first != '$' && !unicode.IsLetter(first) {
1686
+ text = "_" + text
1687
+ }
1688
+ return text
1689
+ }
1690
+
1691
+ func nestiaCoreDiagnostic(site nestiaCoreSite, message string) typiaTransformDiagnostic {
1692
+ line, column := 0, 0
1693
+ if site.File != nil && site.Call != nil {
1694
+ if pos := site.Call.AsNode().Pos(); pos >= 0 {
1695
+ l, c := shimscanner.GetECMALineAndByteOffsetOfPosition(site.File, pos)
1696
+ line, column = l+1, c+1
1697
+ }
1698
+ }
1699
+ return typiaTransformDiagnostic{
1700
+ File: site.FilePath,
1701
+ Line: line,
1702
+ Column: column,
1703
+ Code: "nestia.core." + site.Kind,
1704
+ Message: message,
1705
+ }
1706
+ }
1707
+
1708
+ func nestiaCoreGlobalDiagnostic(code string, message string) typiaTransformDiagnostic {
1709
+ return typiaTransformDiagnostic{
1710
+ Code: code,
1711
+ Message: message,
1712
+ }
1713
+ }