@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.
- package/lib/adaptors/McpAdaptor.js +21 -18
- package/lib/adaptors/McpAdaptor.js.map +1 -1
- package/lib/decorators/EncryptedBody.js +2 -7
- package/lib/decorators/EncryptedBody.js.map +1 -1
- package/lib/decorators/EncryptedRoute.js +1 -1
- package/lib/decorators/EncryptedRoute.js.map +1 -1
- package/lib/decorators/McpRoute.js +1 -1
- package/lib/decorators/McpRoute.js.map +1 -1
- package/lib/decorators/NoTransformConfigurationError.js +2 -2
- package/lib/decorators/PlainBody.js +2 -7
- package/lib/decorators/PlainBody.js.map +1 -1
- package/lib/decorators/SwaggerCustomizer.js +2 -2
- package/lib/decorators/SwaggerCustomizer.js.map +1 -1
- package/lib/decorators/SwaggerExample.d.ts +37 -40
- package/lib/decorators/SwaggerExample.js +24 -26
- package/lib/decorators/SwaggerExample.js.map +1 -1
- package/lib/decorators/TypedBody.js +3 -7
- package/lib/decorators/TypedBody.js.map +1 -1
- package/lib/decorators/TypedException.d.ts +1 -1
- package/lib/decorators/TypedFormData.js +2 -7
- package/lib/decorators/TypedFormData.js.map +1 -1
- package/lib/decorators/TypedQuery.js +2 -9
- package/lib/decorators/TypedQuery.js.map +1 -1
- package/lib/decorators/WebSocketRoute.js +1 -1
- package/lib/decorators/WebSocketRoute.js.map +1 -1
- package/lib/decorators/internal/get_path_and_querify.js +1 -1
- package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
- package/lib/decorators/internal/is_media_type.d.ts +1 -0
- package/lib/decorators/internal/is_media_type.js +11 -0
- package/lib/decorators/internal/is_media_type.js.map +1 -0
- package/lib/decorators/internal/validate_request_query.js.map +1 -1
- package/native/go.mod +1 -1
- package/native/go.sum +32 -32
- package/native/plugin/plan.go +17 -0
- package/native/transform/build.go +41 -80
- package/native/transform/contributor.go +4 -64
- package/native/transform/core_transform.go +42 -705
- package/native/transform/node_transform.go +3 -5
- package/native/transform/printer.go +0 -217
- package/native/transform/run.go +29 -0
- package/native/transform/transform.go +54 -99
- package/native/transform/typia_dependency.go +117 -0
- package/native/transform/typia_fast.go +0 -320
- package/native/transform/typia_registry.go +40 -0
- package/package.json +12 -8
- package/src/adaptors/McpAdaptor.ts +29 -21
- package/src/decorators/EncryptedBody.ts +2 -9
- package/src/decorators/EncryptedRoute.ts +1 -1
- package/src/decorators/McpRoute.ts +2 -5
- package/src/decorators/NoTransformConfigurationError.ts +2 -2
- package/src/decorators/PlainBody.ts +2 -9
- package/src/decorators/SwaggerCustomizer.ts +4 -2
- package/src/decorators/SwaggerExample.ts +38 -41
- package/src/decorators/TypedBody.ts +5 -9
- package/src/decorators/TypedException.ts +1 -1
- package/src/decorators/TypedFormData.ts +7 -9
- package/src/decorators/TypedQuery.ts +7 -12
- package/src/decorators/WebSocketRoute.ts +1 -1
- package/src/decorators/internal/get_path_and_querify.ts +1 -1
- package/src/decorators/internal/is_media_type.ts +10 -0
- package/src/decorators/internal/validate_request_query.ts +2 -1
- package/native/transform/cleanup.go +0 -408
- package/native/transform/exports.go +0 -13
- package/native/transform/rewrite.go +0 -668
- 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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
package/native/transform/run.go
CHANGED
|
@@ -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
|
|
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(
|
|
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 :=
|
|
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
|
+
}
|