@nestia/core 12.0.0-rc.4 → 12.1.0

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 (60) hide show
  1. package/lib/adaptors/McpAdaptor.js +21 -18
  2. package/lib/adaptors/McpAdaptor.js.map +1 -1
  3. package/lib/decorators/EncryptedBody.js +2 -7
  4. package/lib/decorators/EncryptedBody.js.map +1 -1
  5. package/lib/decorators/EncryptedRoute.js +1 -1
  6. package/lib/decorators/EncryptedRoute.js.map +1 -1
  7. package/lib/decorators/McpRoute.js +1 -1
  8. package/lib/decorators/McpRoute.js.map +1 -1
  9. package/lib/decorators/NoTransformConfigurationError.js +2 -2
  10. package/lib/decorators/PlainBody.js +2 -7
  11. package/lib/decorators/PlainBody.js.map +1 -1
  12. package/lib/decorators/SwaggerCustomizer.js +2 -2
  13. package/lib/decorators/SwaggerCustomizer.js.map +1 -1
  14. package/lib/decorators/SwaggerExample.d.ts +37 -40
  15. package/lib/decorators/SwaggerExample.js +24 -26
  16. package/lib/decorators/SwaggerExample.js.map +1 -1
  17. package/lib/decorators/TypedBody.js +3 -7
  18. package/lib/decorators/TypedBody.js.map +1 -1
  19. package/lib/decorators/TypedException.d.ts +2 -2
  20. package/lib/decorators/TypedFormData.js +2 -7
  21. package/lib/decorators/TypedFormData.js.map +1 -1
  22. package/lib/decorators/TypedQuery.js +2 -9
  23. package/lib/decorators/TypedQuery.js.map +1 -1
  24. package/lib/decorators/WebSocketRoute.js +1 -1
  25. package/lib/decorators/WebSocketRoute.js.map +1 -1
  26. package/lib/decorators/internal/get_path_and_querify.js +1 -1
  27. package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
  28. package/lib/decorators/internal/is_media_type.d.ts +1 -0
  29. package/lib/decorators/internal/is_media_type.js +11 -0
  30. package/lib/decorators/internal/is_media_type.js.map +1 -0
  31. package/lib/decorators/internal/validate_request_query.js.map +1 -1
  32. package/native/plugin/plan.go +17 -0
  33. package/native/transform/build.go +39 -80
  34. package/native/transform/contributor.go +4 -64
  35. package/native/transform/core_transform.go +42 -705
  36. package/native/transform/node_transform.go +3 -5
  37. package/native/transform/printer.go +0 -217
  38. package/native/transform/transform.go +2 -91
  39. package/native/transform/typia_fast.go +0 -320
  40. package/package.json +10 -10
  41. package/src/adaptors/McpAdaptor.ts +29 -21
  42. package/src/decorators/EncryptedBody.ts +2 -9
  43. package/src/decorators/EncryptedRoute.ts +1 -1
  44. package/src/decorators/McpRoute.ts +6 -5
  45. package/src/decorators/NoTransformConfigurationError.ts +2 -2
  46. package/src/decorators/PlainBody.ts +2 -9
  47. package/src/decorators/SwaggerCustomizer.ts +8 -2
  48. package/src/decorators/SwaggerExample.ts +43 -45
  49. package/src/decorators/TypedBody.ts +5 -9
  50. package/src/decorators/TypedException.ts +2 -2
  51. package/src/decorators/TypedFormData.ts +7 -9
  52. package/src/decorators/TypedQuery.ts +7 -12
  53. package/src/decorators/WebSocketRoute.ts +1 -1
  54. package/src/decorators/internal/get_path_and_querify.ts +1 -1
  55. package/src/decorators/internal/is_media_type.ts +10 -0
  56. package/src/decorators/internal/validate_request_query.ts +2 -1
  57. package/native/transform/cleanup.go +0 -408
  58. package/native/transform/exports.go +0 -13
  59. package/native/transform/rewrite.go +0 -668
  60. package/native/transform/typia_replacement.go +0 -24
@@ -6,11 +6,8 @@ import (
6
6
  "path/filepath"
7
7
  "regexp"
8
8
  "runtime/debug"
9
- "sort"
10
- "strconv"
11
9
  "strings"
12
10
  "sync"
13
- "unicode"
14
11
 
15
12
  shimast "github.com/microsoft/typescript-go/shim/ast"
16
13
  shimchecker "github.com/microsoft/typescript-go/shim/checker"
@@ -53,15 +50,13 @@ type nestiaCoreTransformState struct {
53
50
  prog *driver.Program
54
51
  options nestiaCoreOptions
55
52
  // 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.
53
+ // generated for the current file. When set, generation result caching is
54
+ // disabled: the cache keys on text, but ec-mode nodes embed per-file
55
+ // NewGeneratedNameForNode imports that cannot be reused verbatim across
56
+ // files. The nil case is defensive every current caller supplies one.
61
57
  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.
58
+ // ec is the emit EmitContext. Threaded into ITypiaContext.Emit so typia's
59
+ // per-programmer factories build emit-tracked nodes.
65
60
  ec *shimprinter.EmitContext
66
61
  cache map[nestiaCoreCacheKey][]string
67
62
  cacheHits int
@@ -130,388 +125,9 @@ func readNestiaCoreOptions(plan plugin.Plan) nestiaCoreOptions {
130
125
  return options
131
126
  }
132
127
 
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
128
  // nestiaCoreMethodArgumentNode builds the single appended decorator-argument
512
129
  // 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.
130
+ // the file-scoped ec-mode ImportProgrammer.
515
131
  func nestiaCoreMethodArgumentNode(
516
132
  prog *driver.Program,
517
133
  importer *nativecontext.ImportProgrammer, ec *shimprinter.EmitContext,
@@ -529,46 +145,6 @@ func nestiaCoreMethodArgumentNode(
529
145
  }
530
146
  })
531
147
  }
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
148
  func nestiaCoreRawDecoratorCall(decorator *shimast.Node) (*shimast.CallExpression, []string, bool) {
573
149
  if decorator == nil || decorator.Kind != NestiaCoreKindDecorator {
574
150
  return nil, nil, false
@@ -705,58 +281,6 @@ func IsNestiaCoreCall(prog *driver.Program, node *shimast.Node) bool {
705
281
  strings.Contains(location, "@nestia/core/src/decorators/") ||
706
282
  strings.Contains(location, "packages/core/src/decorators/")
707
283
  }
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
284
  func nestiaCoreParameterKind(segments []string) string {
761
285
  suffixes := map[string]string{
762
286
  "EncryptedBody": "TypedBody",
@@ -801,45 +325,10 @@ func nestiaCoreMethodKind(segments []string) string {
801
325
  }
802
326
  }
803
327
 
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
328
  // nestiaCoreParameterArgumentNodes builds the appended decorator-argument nodes
838
329
  // 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.
330
+ // it is the shared ec-mode importer, so the validator's runtime references
331
+ // resolve to tsgo-aliased namespace imports.
843
332
  func nestiaCoreParameterArgumentNodes(
844
333
  prog *driver.Program,
845
334
  importer *nativecontext.ImportProgrammer, ec *shimprinter.EmitContext,
@@ -1198,9 +687,8 @@ func nestiaCoreGenerateTypedQueryRoute(prog *driver.Program, importer *nativecon
1198
687
  // validator generation. The importer argument is the file-scoped ImportProgrammer:
1199
688
  // on the AST-integration emit path it is the shared, ec-mode importer (so every
1200
689
  // 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.
690
+ // aliases, and all injected imports collapse into one ToStatements() set). A nil
691
+ // importer falls back to a throwaway one; no current caller relies on it.
1204
692
  func nestiaCoreTypiaContext(prog *driver.Program, importer *nativecontext.ImportProgrammer, ec *shimprinter.EmitContext, numeric bool, finite bool, functional bool) nativecontext.ITypiaContext {
1205
693
  if importer == nil {
1206
694
  importer = nativecontext.NewImportProgrammer(nativecontext.ImportProgrammer_IOptions{
@@ -1222,8 +710,7 @@ func nestiaCoreTypiaContext(prog *driver.Program, importer *nativecontext.Import
1222
710
  // Seed the emit context so typia's per-programmer factories
1223
711
  // (EmitFactoryOf(..., Context.Emit)) build emit-tracked nodes; without it
1224
712
  // 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.
713
+ // tsgo's MarkLinkedReferences pass nil-panics during emit.
1227
714
  Emit: ec,
1228
715
  }
1229
716
  }
@@ -1521,55 +1008,6 @@ func safeNestiaCoreGenerateNode(generator func() (*shimast.Node, error)) (node *
1521
1008
  return generator()
1522
1009
  }
1523
1010
 
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
1011
  var nestiaCoreSingleParameterArrowPattern = regexp.MustCompile(`(^|[\s(=,:?])([A-Za-z_$][A-Za-z0-9_$]*) =>`)
1574
1012
 
1575
1013
  func NestiaCoreMethodReturnType(prog *driver.Program, node *shimast.Node) *shimchecker.Type {
@@ -1768,19 +1206,41 @@ func callArgumentBounds(source string, call *shimast.CallExpression) (int, int,
1768
1206
  return 0, 0, false
1769
1207
  }
1770
1208
  open += start
1771
- close, ok := matchNativeParen(source, open)
1209
+ close, ok := matchClosingParen(source, open)
1772
1210
  return open, close, ok
1773
1211
  }
1774
1212
 
1775
- func appendArgumentsText(current string, arguments []string) string {
1776
- current = strings.TrimSpace(current)
1777
- next := strings.Join(arguments, ", ")
1778
- if current == "" {
1779
- return next
1213
+ // matchClosingParen returns the index of the ')' closing the '(' at pos,
1214
+ // skipping over quoted and template spans so a parenthesis inside a string
1215
+ // literal argument cannot unbalance the scan.
1216
+ func matchClosingParen(text string, pos int) (int, bool) {
1217
+ if pos >= len(text) || text[pos] != '(' {
1218
+ return 0, false
1219
+ }
1220
+ depth := 1
1221
+ for i := pos + 1; i < len(text); i++ {
1222
+ switch text[i] {
1223
+ case '(':
1224
+ depth++
1225
+ case ')':
1226
+ depth--
1227
+ if depth == 0 {
1228
+ return i, true
1229
+ }
1230
+ case '"', '\'', '`':
1231
+ q := text[i]
1232
+ j := i + 1
1233
+ for j < len(text) && text[j] != q {
1234
+ if text[j] == '\\' {
1235
+ j++
1236
+ }
1237
+ j++
1238
+ }
1239
+ i = j
1240
+ }
1780
1241
  }
1781
- return current + ", " + next
1242
+ return 0, false
1782
1243
  }
1783
-
1784
1244
  func NestiaCoreExpressionSegments(node *shimast.Node) []string {
1785
1245
  if node == nil {
1786
1246
  return nil
@@ -1804,15 +1264,6 @@ func NestiaCoreExpressionSegments(node *shimast.Node) []string {
1804
1264
  }
1805
1265
  return nil
1806
1266
  }
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
1267
  func nestiaCoreTypeName(prog *driver.Program, typ *shimchecker.Type) *string {
1817
1268
  name := nestiaCoreTypeNameText(prog, typ)
1818
1269
  return &name
@@ -1851,126 +1302,12 @@ func nestiaCoreSegmentsHaveSuffix(segments []string, suffix []string) bool {
1851
1302
  return true
1852
1303
  }
1853
1304
 
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
1305
  type commonJSImportIdentifierSubstitutionsCacheEntry struct {
1884
1306
  value map[string]string
1885
1307
  }
1886
1308
 
1887
1309
  var commonJSImportIdentifierSubstitutionsCache sync.Map
1888
1310
 
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
1311
  func nestiaCoreDiagnostic(site nestiaCoreSite, message string) Diagnostic {
1975
1312
  line, column := 0, 0
1976
1313
  if site.File != nil && site.Call != nil {