@nestia/core 12.0.0-dev.20260601.1 → 12.0.0-dev.20260612.2

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