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