@nestia/core 12.0.0 → 13.0.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 (65) 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 +1 -1
  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/go.mod +1 -1
  33. package/native/go.sum +32 -32
  34. package/native/plugin/plan.go +17 -0
  35. package/native/transform/build.go +41 -80
  36. package/native/transform/contributor.go +4 -64
  37. package/native/transform/core_transform.go +42 -705
  38. package/native/transform/node_transform.go +3 -5
  39. package/native/transform/printer.go +0 -217
  40. package/native/transform/run.go +29 -0
  41. package/native/transform/transform.go +54 -99
  42. package/native/transform/typia_dependency.go +117 -0
  43. package/native/transform/typia_fast.go +0 -320
  44. package/native/transform/typia_registry.go +40 -0
  45. package/package.json +12 -8
  46. package/src/adaptors/McpAdaptor.ts +29 -21
  47. package/src/decorators/EncryptedBody.ts +2 -9
  48. package/src/decorators/EncryptedRoute.ts +1 -1
  49. package/src/decorators/McpRoute.ts +2 -5
  50. package/src/decorators/NoTransformConfigurationError.ts +2 -2
  51. package/src/decorators/PlainBody.ts +2 -9
  52. package/src/decorators/SwaggerCustomizer.ts +4 -2
  53. package/src/decorators/SwaggerExample.ts +38 -41
  54. package/src/decorators/TypedBody.ts +5 -9
  55. package/src/decorators/TypedException.ts +1 -1
  56. package/src/decorators/TypedFormData.ts +7 -9
  57. package/src/decorators/TypedQuery.ts +7 -12
  58. package/src/decorators/WebSocketRoute.ts +1 -1
  59. package/src/decorators/internal/get_path_and_querify.ts +1 -1
  60. package/src/decorators/internal/is_media_type.ts +10 -0
  61. package/src/decorators/internal/validate_request_query.ts +2 -1
  62. package/native/transform/cleanup.go +0 -408
  63. package/native/transform/exports.go +0 -13
  64. package/native/transform/rewrite.go +0 -668
  65. package/native/transform/typia_replacement.go +0 -24
@@ -15,8 +15,7 @@ import (
15
15
  // runtime references resolve to namespace imports tsgo's module-transform
16
16
  // aliases), injects those imports, and normalizes the synthetic operator tokens
17
17
  // typia's programmers leave nil. A diagnostic is appended for any site whose
18
- // generation raises a (recovered) transform error, matching the legacy text
19
- // path's swallow-and-report behavior.
18
+ // generation raises a (recovered) transform error.
20
19
  //
21
20
  // addDiagnostic receives one entry per failed site; the build command turns a
22
21
  // non-empty diagnostic slice into a non-zero exit.
@@ -97,8 +96,7 @@ func nestiaCoreNodeTransform(
97
96
  result = nestiaCoreInjectImports(result, importer.ToStatements(), ec)
98
97
  // The node path prints through tsgo's printer, which dereferences the
99
98
  // conditional ?/: operator tokens typia's programmers leave nil; the
100
- // legacy text path filled them via normalizeNestiaSyntheticTokens before
101
- // printing, so do the same here.
99
+ // so normalizeNestiaSyntheticTokens fills them before printing.
102
100
  normalizeNestiaSyntheticTokens(result.AsNode())
103
101
  return result
104
102
  }
@@ -211,7 +209,7 @@ func nestiaCoreCollectMethodReplacements(
211
209
  }
212
210
  // @WebSocketRoute carries no injected validator (it is not a method kind),
213
211
  // but its acceptor/driver parameter shapes are validated here, mirroring the
214
- // legacy visitNestiaCoreNode path. Validate against the parse-tree method so
212
+ // collection pass. Validate against the parse-tree method so
215
213
  // parameter type text resolves even when typia rebuilt this method.
216
214
  if len(canonical) != 0 && canonical[len(canonical)-1] == "WebSocketRoute" {
217
215
  for _, diag := range validateNestiaCoreWebSocketRoute(state.prog, context, nestiaCoreOriginalNode(state.ec, node), call, canonical) {
@@ -2,227 +2,10 @@ package transform
2
2
 
3
3
  import (
4
4
  shimast "github.com/microsoft/typescript-go/shim/ast"
5
- shimprinter "github.com/microsoft/typescript-go/shim/printer"
6
5
  )
7
6
 
8
7
  const nestiaKindColonToken = shimast.KindQuestionToken + 1
9
8
 
10
- func emitNestiaWithIdentifierSubstitutions(
11
- node *shimast.Node,
12
- sourceFile *shimast.SourceFile,
13
- substitutions map[string]string,
14
- ) string {
15
- node = stripNestiaTypeSyntax(node)
16
- node = rewriteNestiaIdentifiers(node, substitutions)
17
- normalizeNestiaSyntheticTokens(node)
18
- return emitNestiaNode(node, sourceFile)
19
- }
20
-
21
- func emitNestiaPreservingTypesWithIdentifierSubstitutions(
22
- node *shimast.Node,
23
- sourceFile *shimast.SourceFile,
24
- substitutions map[string]string,
25
- ) string {
26
- node = rewriteNestiaIdentifiers(node, substitutions)
27
- normalizeNestiaSyntheticTokens(node)
28
- return emitNestiaNode(node, sourceFile)
29
- }
30
-
31
- func emitNestiaNode(node *shimast.Node, sourceFile *shimast.SourceFile) string {
32
- return shimprinter.NewPrinter(shimprinter.PrinterOptions{
33
- RemoveComments: true,
34
- NewLine: 2,
35
- }, shimprinter.PrintHandlers{}, nil).Emit(node, sourceFile)
36
- }
37
-
38
- func stripNestiaTypeSyntax(node *shimast.Node) *shimast.Node {
39
- if node == nil {
40
- return nil
41
- }
42
- factory := shimast.NewNodeFactory(shimast.NodeFactoryHooks{})
43
- var visitor *shimast.NodeVisitor
44
- visitor = shimast.NewNodeVisitor(func(current *shimast.Node) *shimast.Node {
45
- if current == nil {
46
- return nil
47
- }
48
- switch current.Kind {
49
- case shimast.KindAsExpression, shimast.KindSatisfiesExpression, shimast.KindTypeAssertionExpression, shimast.KindNonNullExpression:
50
- return visitor.VisitNode(current.Expression())
51
- case shimast.KindVariableDeclaration:
52
- decl := current.AsVariableDeclaration()
53
- return factory.UpdateVariableDeclaration(
54
- decl,
55
- visitor.VisitNode(decl.Name()),
56
- nil,
57
- nil,
58
- visitor.VisitNode(decl.Initializer),
59
- )
60
- case shimast.KindParameter:
61
- parameter := current.AsParameterDeclaration()
62
- return factory.UpdateParameterDeclaration(
63
- parameter,
64
- visitor.VisitModifiers(parameter.Modifiers()),
65
- visitor.VisitNode(parameter.DotDotDotToken),
66
- visitor.VisitNode(parameter.Name()),
67
- nil,
68
- nil,
69
- visitor.VisitNode(parameter.Initializer),
70
- )
71
- case shimast.KindArrowFunction:
72
- arrow := current.AsArrowFunction()
73
- return factory.UpdateArrowFunction(
74
- arrow,
75
- visitor.VisitModifiers(arrow.Modifiers()),
76
- nil,
77
- visitor.VisitNodes(arrow.Parameters),
78
- nil,
79
- nil,
80
- visitor.VisitNode(arrow.EqualsGreaterThanToken),
81
- visitor.VisitNode(arrow.Body),
82
- )
83
- case shimast.KindCallExpression:
84
- call := current.AsCallExpression()
85
- return factory.UpdateCallExpression(
86
- call,
87
- visitor.VisitNode(call.Expression),
88
- visitor.VisitNode(call.QuestionDotToken),
89
- nil,
90
- visitor.VisitNodes(call.Arguments),
91
- call.Flags,
92
- )
93
- case shimast.KindNewExpression:
94
- expr := current.AsNewExpression()
95
- return factory.UpdateNewExpression(
96
- expr,
97
- visitor.VisitNode(expr.Expression),
98
- nil,
99
- visitor.VisitNodes(expr.Arguments),
100
- )
101
- default:
102
- return visitor.VisitEachChild(current)
103
- }
104
- }, factory, shimast.NodeVisitorHooks{})
105
- return visitor.VisitNode(node)
106
- }
107
-
108
- func rewriteNestiaIdentifiers(
109
- node *shimast.Node,
110
- substitutions map[string]string,
111
- ) *shimast.Node {
112
- if node == nil || len(substitutions) == 0 {
113
- return node
114
- }
115
- factory := shimast.NewNodeFactory(shimast.NodeFactoryHooks{})
116
- var visitor *shimast.NodeVisitor
117
- visitor = shimast.NewNodeVisitor(func(current *shimast.Node) *shimast.Node {
118
- if current == nil {
119
- return nil
120
- }
121
- switch current.Kind {
122
- case shimast.KindIdentifier:
123
- if replacement, ok := substitutions[current.Text()]; ok {
124
- return rewriteNestiaIdentifierExpression(factory, replacement)
125
- }
126
- return current
127
- case shimast.KindVariableDeclaration:
128
- decl := current.AsVariableDeclaration()
129
- return factory.UpdateVariableDeclaration(
130
- decl,
131
- decl.Name(),
132
- nil,
133
- nil,
134
- visitor.VisitNode(decl.Initializer),
135
- )
136
- case shimast.KindParameter:
137
- parameter := current.AsParameterDeclaration()
138
- return factory.UpdateParameterDeclaration(
139
- parameter,
140
- parameter.Modifiers(),
141
- parameter.DotDotDotToken,
142
- parameter.Name(),
143
- nil,
144
- nil,
145
- visitor.VisitNode(parameter.Initializer),
146
- )
147
- case shimast.KindPropertyAssignment:
148
- assignment := current.AsPropertyAssignment()
149
- return factory.UpdatePropertyAssignment(
150
- assignment,
151
- assignment.Modifiers(),
152
- assignment.Name(),
153
- assignment.PostfixToken,
154
- nil,
155
- visitor.VisitNode(assignment.Initializer),
156
- )
157
- case shimast.KindShorthandPropertyAssignment:
158
- assignment := current.AsShorthandPropertyAssignment()
159
- name := assignment.Name()
160
- if name != nil && name.Kind == shimast.KindIdentifier {
161
- if replacement, ok := substitutions[name.Text()]; ok &&
162
- assignment.ObjectAssignmentInitializer == nil {
163
- return factory.NewPropertyAssignment(
164
- assignment.Modifiers(),
165
- name,
166
- assignment.PostfixToken,
167
- nil,
168
- rewriteNestiaIdentifierExpression(factory, replacement),
169
- )
170
- }
171
- }
172
- return factory.UpdateShorthandPropertyAssignment(
173
- assignment,
174
- assignment.Modifiers(),
175
- name,
176
- assignment.PostfixToken,
177
- nil,
178
- nil,
179
- visitor.VisitNode(assignment.ObjectAssignmentInitializer),
180
- )
181
- case shimast.KindPropertyAccessExpression:
182
- access := current.AsPropertyAccessExpression()
183
- return factory.UpdatePropertyAccessExpression(
184
- access,
185
- visitor.VisitNode(access.Expression),
186
- access.QuestionDotToken,
187
- access.Name(),
188
- access.Flags,
189
- )
190
- default:
191
- return visitor.VisitEachChild(current)
192
- }
193
- }, factory, shimast.NodeVisitorHooks{})
194
- return visitor.VisitNode(node)
195
- }
196
-
197
- func rewriteNestiaIdentifierExpression(
198
- factory *shimast.NodeFactory,
199
- replacement string,
200
- ) *shimast.Node {
201
- parts := []string{}
202
- start := 0
203
- for i := 0; i <= len(replacement); i++ {
204
- if i == len(replacement) || replacement[i] == '.' {
205
- if start < i {
206
- parts = append(parts, replacement[start:i])
207
- }
208
- start = i + 1
209
- }
210
- }
211
- if len(parts) == 0 {
212
- return factory.NewIdentifier(replacement)
213
- }
214
- output := factory.NewIdentifier(parts[0])
215
- for _, part := range parts[1:] {
216
- output = factory.NewPropertyAccessExpression(
217
- output,
218
- nil,
219
- factory.NewIdentifier(part),
220
- shimast.NodeFlagsNone,
221
- )
222
- }
223
- return output
224
- }
225
-
226
9
  func normalizeNestiaSyntheticTokens(node *shimast.Node) {
227
10
  if node == nil {
228
11
  return
@@ -42,6 +42,35 @@ func Run(args []string) (code int) {
42
42
  return run(args)
43
43
  }
44
44
 
45
+ // RunWithOutput is Run with the host's stdout and stderr writers redirected for
46
+ // the duration of the call, then restored.
47
+ //
48
+ // The project-mode `transform` subcommand publishes its whole result — the
49
+ // transformed sources, the diagnostics, and the reference graph — as one JSON
50
+ // envelope on stdout, and unlike the single-file path it has no `--out` seam. An
51
+ // exit code is therefore the only thing an external test module can observe
52
+ // about it, which is not enough to pin what the envelope carries. This makes the
53
+ // envelope itself observable without giving the CLI a flag ttsc never passes.
54
+ //
55
+ // Restoring inside the call keeps the redirect scoped: a nil writer is ignored,
56
+ // so a caller can redirect one stream and leave the other on the process. The
57
+ // writers are package state, so concurrent calls would interleave; the host runs
58
+ // one command per process and Go tests in one package run sequentially unless
59
+ // they opt into t.Parallel.
60
+ func RunWithOutput(args []string, out io.Writer, errOut io.Writer) int {
61
+ previousOut, previousErr := stdout, stderr
62
+ defer func() {
63
+ stdout, stderr = previousOut, previousErr
64
+ }()
65
+ if out != nil {
66
+ stdout = out
67
+ }
68
+ if errOut != nil {
69
+ stderr = errOut
70
+ }
71
+ return Run(args)
72
+ }
73
+
45
74
  func run(args []string) int {
46
75
  if len(args) == 0 {
47
76
  return runHelp(nil)
@@ -8,18 +8,40 @@ import (
8
8
  "os"
9
9
  "path/filepath"
10
10
  "regexp"
11
- "sort"
12
11
  "strings"
13
12
 
14
13
  shimast "github.com/microsoft/typescript-go/shim/ast"
15
14
  shimprinter "github.com/microsoft/typescript-go/shim/printer"
16
15
  "github.com/samchon/nestia/packages/core/native/plugin"
17
16
  "github.com/samchon/ttsc/packages/ttsc/driver"
17
+ schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
18
18
  )
19
19
 
20
20
  type transformProjectOutput struct {
21
21
  Diagnostics []transformCompilerDiagnostic `json:"diagnostics,omitempty"`
22
22
  TypeScript map[string]string `json:"typescript"`
23
+ // Graph is the host-owned reference graph of the loaded program produced by
24
+ // the ttsc driver SDK (driver.NewTransformGraph): direct resolved reference
25
+ // edges, ambient global-scope files, the tsconfig extends chain, and the
26
+ // module-resolution candidates that outrank a selected target. Keys and
27
+ // values share TypeScript's keying through driver.TransformOutputKey, so a
28
+ // consumer joins the sections by key.
29
+ //
30
+ // A bundler erases type-only imports from its own module graph, so without
31
+ // this section nothing tells a persistent filesystem cache that a generated
32
+ // validator depends on the DTO declaration it was generated from, and the
33
+ // cache replays the stale module after the DTO's type changes. Stamping it
34
+ // also moves `@ttsc/unplugin` off the whole-snapshot revalidation path onto
35
+ // its narrow per-file one. Omitted only for a nil or unloaded program, which
36
+ // cannot happen here: LoadProgram failures return before this point.
37
+ Graph *driver.TransformGraph `json:"graph,omitempty"`
38
+ // Dependencies maps each transformed file (same keys as TypeScript) to the
39
+ // source files owning the declarations typia's metadata analysis consulted
40
+ // while generating that file's validators and stringifiers. A consumer
41
+ // unions them with Graph rather than choosing between them, so the list is
42
+ // additive: an omission costs nothing, and narrowing would require the
43
+ // separate `dependenciesComplete` declaration this envelope does not make.
44
+ Dependencies map[string][]string `json:"dependencies,omitempty"`
23
45
  }
24
46
 
25
47
  type transformCompilerDiagnostic struct {
@@ -69,18 +91,19 @@ func runTransform(args []string) int {
69
91
  return 2
70
92
  }
71
93
  defer prog.Close()
94
+ releaseTypiaRegistries := registerTypiaDefaultLibraryClassifier(prog)
95
+ defer releaseTypiaRegistries()
72
96
 
73
97
  // AST-integration source-to-source: typia's, core's, and any linked
74
98
  // contributor's per-file node transformers run inside one shared EmitContext
75
99
  // and the result SourceFile is printed back as TypeScript (no JS script
76
100
  // transformers), mirroring typia's `transform` subcommand. Injected namespace
77
- // imports stay as ES imports the caller can type-strip per file, so there is
78
- // no text-splice RewriteSet.
101
+ // imports stay as ES imports the caller can type-strip per file.
79
102
  transformDiags := []Diagnostic{}
80
103
  addDiagnostic := func(diag Diagnostic) {
81
104
  transformDiags = append(transformDiags, diag)
82
105
  }
83
- typiaTransform := nestiaTypiaNodeTransform(prog, readTypiaPluginOptions(cwd, *tsconfigPath), addDiagnostic)
106
+ typiaTransform := nestiaTypiaNodeTransform(prog, readTypiaPluginOptions(plan), addDiagnostic)
84
107
  coreTransform := nestiaCoreNodeTransform(prog, plan, addDiagnostic)
85
108
  contributorTransforms, contributorDiags := collectContributorEmitTransforms(prog, plan)
86
109
  if len(contributorDiags) > 0 {
@@ -125,16 +148,42 @@ func runTransformProject(
125
148
  Diagnostics: []transformCompilerDiagnostic{},
126
149
  TypeScript: map[string]string{},
127
150
  }
151
+ // Compute the reference graph from the loaded program before the transform
152
+ // loop runs: the graph must describe the original sources' resolved
153
+ // references, which are the transform's inputs, and the loop's printing pass
154
+ // sets parent pointers on the node tree it walks. ttsc's own `api-transform`
155
+ // host stamps it at the same point.
156
+ output.Graph = driver.NewTransformGraph(prog, cwd)
157
+ // `dependencies` is the second, narrower channel: the declaration files
158
+ // typia's analysis actually read for each file, reported alongside the graph
159
+ // rather than instead of it. The two are unioned by the consumer, so this
160
+ // list can only add inputs.
161
+ //
162
+ // The envelope deliberately declares no `dependenciesComplete`. That field
163
+ // transfers responsibility -- a listed file stops watching everything the
164
+ // reference closure carries -- and this host cannot honor it: whether a file
165
+ // is transformed at all depends on symbol resolutions that no listener
166
+ // reports, including the negative ones, and typia's own trigger set belongs
167
+ // to typia rather than to this host. See samchon/typia#2357.
168
+ collector := newTransformDependencyCollector(cwd, func(fileName string) bool {
169
+ sf := prog.SourceFile(fileName)
170
+ return sf != nil && prog.TSProgram.IsLibFile(sf)
171
+ })
172
+ schemametadata.MetadataDependency_listen(prog.Checker, collector.Touch)
173
+ defer schemametadata.MetadataDependency_release(prog.Checker)
128
174
  for _, sf := range prog.SourceFiles() {
129
175
  if sf.IsDeclarationFile {
130
176
  continue
131
177
  }
132
- key := sourceFileKey(cwd, filepath.ToSlash(sf.FileName()))
178
+ key := driver.TransformOutputKey(cwd, sf.FileName())
133
179
  if filepath.IsAbs(key) || key == ".." || strings.HasPrefix(key, "../") {
134
180
  continue
135
181
  }
182
+ collector.Begin(key)
136
183
  output.TypeScript[key] = transformFileToTypeScript(prog, transforms, sf)
184
+ collector.End()
137
185
  }
186
+ output.Dependencies = collector.ToJSON()
138
187
  for _, diag := range *transformDiags {
139
188
  output.Diagnostics = append(output.Diagnostics, transformDiagnosticToCompilerDiagnostic(diag))
140
189
  }
@@ -196,32 +245,6 @@ func writeSingleOutput(text, outPath string) int {
196
245
  return 0
197
246
  }
198
247
 
199
- type SourceRewrite struct {
200
- start int
201
- end int
202
- replacement string
203
- }
204
-
205
- func ApplySourceRewrites(source string, rewrites []SourceRewrite) (string, error) {
206
- sort.SliceStable(rewrites, func(i, j int) bool {
207
- return rewrites[i].start > rewrites[j].start
208
- })
209
- for i := 0; i < len(rewrites)-1; i++ {
210
- if rewrites[i+1].end > rewrites[i].start {
211
- return "", fmt.Errorf("overlapping rewrites: [%d,%d) vs [%d,%d)",
212
- rewrites[i+1].start, rewrites[i+1].end, rewrites[i].start, rewrites[i].end)
213
- }
214
- }
215
- output := source
216
- for _, rewrite := range rewrites {
217
- if rewrite.start < 0 || rewrite.end < rewrite.start || rewrite.end > len(output) {
218
- return "", fmt.Errorf("invalid rewrite range [%d,%d)", rewrite.start, rewrite.end)
219
- }
220
- output = output[:rewrite.start] + rewrite.replacement + output[rewrite.end:]
221
- }
222
- return output, nil
223
- }
224
-
225
248
  func SourceFileText(target any) (string, bool) {
226
249
  type sourceText interface {
227
250
  Text() string
@@ -244,79 +267,11 @@ var (
244
267
  cleanupInputIsParenPattern = regexp.MustCompile(`input is \(([A-Za-z_$][A-Za-z0-9_$.]*)\)`)
245
268
  cleanupCollapseBlankCallPattern = regexp.MustCompile(`\n\n([A-Za-z_$][A-Za-z0-9_$]*\([^;\n]*\);?)`)
246
269
  )
247
-
248
- func cleanupTypeScriptTransformText(text string) string {
249
- text = cleanupTransformedText(text)
250
- text = normalizeParenthesizedTypeAnnotations(text)
251
- text = cleanupImportTypeBlockPattern.ReplaceAllStringFunc(text, func(line string) string {
252
- return cleanupImportTypeLinePattern.ReplaceAllString(line, "import type { $1 } from")
253
- })
254
- text = cleanupImportBlankLinePattern.ReplaceAllString(text, "$1$2")
255
- text = strings.ReplaceAll(text, "=(() =>", "= (() =>")
256
- text = strings.ReplaceAll(text, ": (any) =>", ": any =>")
257
- text = strings.ReplaceAll(text, ": (boolean) =>", ": boolean =>")
258
- text = cleanupInputIsParenPattern.ReplaceAllString(text, "input is $1")
259
- text = strings.ReplaceAll(text, "return (success ? ", "return success ? ")
260
- text = strings.ReplaceAll(text, "}) as any;", "} as any;")
261
- text = strings.ReplaceAll(text, "(() => {\n const ", "(() => { const ")
262
- text = strings.ReplaceAll(text, "(() => {\n let ", "(() => { let ")
263
- text = strings.ReplaceAll(text, "(() => {\n return ", "(() => { return ")
264
- text = strings.ReplaceAll(text, ";\n const ", "; const ")
265
- text = strings.ReplaceAll(text, ";\n let ", "; let ")
266
- text = strings.ReplaceAll(text, ";\n return ", "; return ")
267
- text = strings.ReplaceAll(text, "\n };\n})()", "\n}; })()")
268
- text = strings.ReplaceAll(text, "\n });\n})()", "\n}); })()")
269
- text = strings.ReplaceAll(text, "\n }); let ", "\n}); let ")
270
- text = strings.ReplaceAll(text, ";\n})()", "; })()")
271
- text = strings.ReplaceAll(text, "\n ", "\n ")
272
- text = cleanupCollapseBlankCallPattern.ReplaceAllString(text, "\n$1")
273
- trimmed := strings.TrimRight(text, " \t\r\n")
274
- if strings.HasSuffix(trimmed, ")") && !strings.HasSuffix(trimmed, ";") {
275
- return trimmed + ";\n"
276
- }
277
- if text != "" && !strings.HasSuffix(text, "\n") {
278
- return text + "\n"
279
- }
280
- return text
281
- }
282
-
283
270
  var (
284
271
  normalizeParenArrowTypePattern = regexp.MustCompile(`: \(([A-Za-z_$][A-Za-z0-9_$.]*(<[^()\n;{}]*>)?)\)(\s*=>)`)
285
272
  normalizeParenNullishPattern = regexp.MustCompile(`\| \((null|undefined)\)`)
286
273
  )
287
274
 
288
- func normalizeParenthesizedTypeAnnotations(text string) string {
289
- text = normalizeParenArrowTypePattern.ReplaceAllString(text, ": $1$3")
290
- text = normalizeParenNullishPattern.ReplaceAllString(text, "| $1")
291
- return text
292
- }
293
-
294
- func sourceFileKey(cwd string, file string) string {
295
- rel, err := filepath.Rel(cwd, filepath.FromSlash(file))
296
- if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
297
- return filepath.ToSlash(file)
298
- }
299
- return filepath.ToSlash(rel)
300
- }
301
-
302
- func newTransformCompilerDiagnostic(
303
- file string,
304
- code string,
305
- message string,
306
- ) transformCompilerDiagnostic {
307
- var ptr *string
308
- if file != "" {
309
- normalized := filepath.ToSlash(file)
310
- ptr = &normalized
311
- }
312
- return transformCompilerDiagnostic{
313
- File: ptr,
314
- Category: "error",
315
- Code: code,
316
- MessageText: message,
317
- }
318
- }
319
-
320
275
  func transformDiagnosticToCompilerDiagnostic(
321
276
  diag Diagnostic,
322
277
  ) transformCompilerDiagnostic {
@@ -0,0 +1,117 @@
1
+ package transform
2
+
3
+ import (
4
+ "sort"
5
+ "strings"
6
+
7
+ "github.com/samchon/ttsc/packages/ttsc/driver"
8
+ )
9
+
10
+ // transformDependencyCollector accumulates the declaration files typia's
11
+ // metadata analysis reports while one project file is transformed, attributing
12
+ // them to that file's envelope key.
13
+ //
14
+ // These are the envelope's `dependencies`: the source files owning the
15
+ // declarations the analysis actually read to generate that file's validators and
16
+ // stringifiers. A consumer unions them with the host-owned reference graph, so
17
+ // the list is additive and an omission costs nothing; it never narrows anything
18
+ // on its own. Narrowing is what `dependenciesComplete` would do, and this
19
+ // envelope does not declare it -- see the note in runTransformProject.
20
+ type transformDependencyCollector struct {
21
+ cwd string
22
+ current string
23
+ files map[string]map[string]bool
24
+ // isLibraryFile reports the compiler's own classification of a file as a
25
+ // default library. Classification must come from the program, never from a
26
+ // file-name pattern, for the reason registerTypiaDefaultLibraryClassifier
27
+ // records: a project's own ambient declaration file may legitimately be
28
+ // named `lib.custom.d.ts`, and dropping it by base name silently loses cache
29
+ // invalidation for the types it declares.
30
+ isLibraryFile func(fileName string) bool
31
+ // values memoizes fileName -> envelope value ("" for a dropped file). The
32
+ // listener reports the same declaration files repeatedly across call sites.
33
+ values map[string]string
34
+ }
35
+
36
+ func newTransformDependencyCollector(
37
+ cwd string,
38
+ isLibraryFile func(fileName string) bool,
39
+ ) *transformDependencyCollector {
40
+ return &transformDependencyCollector{
41
+ cwd: cwd,
42
+ isLibraryFile: isLibraryFile,
43
+ files: map[string]map[string]bool{},
44
+ values: map[string]string{},
45
+ }
46
+ }
47
+
48
+ // Begin attributes subsequent touches to the given envelope key.
49
+ func (collector *transformDependencyCollector) Begin(key string) {
50
+ collector.current = key
51
+ }
52
+
53
+ // End closes the window Begin opened, so a touch arriving between two files is
54
+ // dropped rather than charged against whichever file happened to run last.
55
+ func (collector *transformDependencyCollector) End() {
56
+ collector.current = ""
57
+ }
58
+
59
+ // Touch records one consulted declaration file for the current key. Default
60
+ // library files, virtual URI sources (tsgo's `bundled:///` libraries), and the
61
+ // transformed file itself are dropped: the first two are toolchain-versioned
62
+ // rather than project inputs, and the third is already the cache key.
63
+ func (collector *transformDependencyCollector) Touch(fileName string) {
64
+ if collector.current == "" {
65
+ return
66
+ }
67
+ value, memoized := collector.values[fileName]
68
+ if memoized == false {
69
+ value = collector.value(fileName)
70
+ collector.values[fileName] = value
71
+ }
72
+ if value == "" || value == collector.current {
73
+ return
74
+ }
75
+ set := collector.files[collector.current]
76
+ if set == nil {
77
+ set = map[string]bool{}
78
+ collector.files[collector.current] = set
79
+ }
80
+ set[value] = true
81
+ }
82
+
83
+ // value renders one reported file as its envelope key, or "" when it is dropped.
84
+ // The key is driver.TransformOutputKey, the same one `typescript` and `graph`
85
+ // use, so a consumer joins every section by key.
86
+ func (collector *transformDependencyCollector) value(fileName string) string {
87
+ if strings.Contains(fileName, "://") || collector.isLibraryFile(fileName) {
88
+ return ""
89
+ }
90
+ return driver.TransformOutputKey(collector.cwd, fileName)
91
+ }
92
+
93
+ // ToJSON renders the collected sets as the envelope's `dependencies` map with
94
+ // deterministically sorted values, or nil when nothing was collected. The nil
95
+ // keeps the field off the wire entirely, which is what an envelope with nothing
96
+ // to report must send.
97
+ func (collector *transformDependencyCollector) ToJSON() map[string][]string {
98
+ if len(collector.files) == 0 {
99
+ return nil
100
+ }
101
+ output := map[string][]string{}
102
+ for key, set := range collector.files {
103
+ if len(set) == 0 {
104
+ continue
105
+ }
106
+ values := make([]string, 0, len(set))
107
+ for value := range set {
108
+ values = append(values, value)
109
+ }
110
+ sort.Strings(values)
111
+ output[key] = values
112
+ }
113
+ if len(output) == 0 {
114
+ return nil
115
+ }
116
+ return output
117
+ }