@nestia/core 11.2.1 → 12.0.0-dev.20260521.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 +10 -5
- package/lib/transform.js +64 -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/main.go +11 -0
- package/native/go.mod +32 -0
- package/native/go.sum +54 -0
- package/native/plugin/plan.go +102 -0
- package/native/transform/ast.go +32 -0
- package/native/transform/build.go +413 -0
- package/native/transform/cleanup.go +408 -0
- package/native/transform/cleanup_test.go +76 -0
- package/native/transform/commonjs_import_alias_test.go +49 -0
- package/native/transform/core_dispatch_test.go +127 -0
- package/native/transform/core_querify.go +227 -0
- package/native/transform/core_transform.go +1713 -0
- package/native/transform/core_websocket.go +115 -0
- package/native/transform/exports.go +13 -0
- package/native/transform/path_rewrite.go +285 -0
- package/native/transform/path_rewrite_test.go +243 -0
- package/native/transform/printer.go +244 -0
- package/native/transform/rewrite.go +662 -0
- package/native/transform/rewrite_test.go +118 -0
- package/native/transform/rewrite_unique_base_test.go +48 -0
- package/native/transform/run.go +72 -0
- package/native/transform/transform.go +376 -0
- package/native/transform/typia_fast.go +326 -0
- package/native/transform/typia_replacement.go +24 -0
- package/native/transform.cjs +43 -0
- package/package.json +28 -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 +101 -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,662 @@
|
|
|
1
|
+
package transform
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"sort"
|
|
7
|
+
"strings"
|
|
8
|
+
"unicode"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
type nativeRewrite struct {
|
|
12
|
+
FilePath string
|
|
13
|
+
RootName string
|
|
14
|
+
Namespaces []string
|
|
15
|
+
Method string
|
|
16
|
+
Replacement string
|
|
17
|
+
ConsumeParens bool
|
|
18
|
+
AppendArguments []string
|
|
19
|
+
TargetExpressionCandidates []string
|
|
20
|
+
SourceStart int
|
|
21
|
+
ExpectedArgumentCount *int
|
|
22
|
+
ExpectedArgumentsText string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type nativeRewriteSet struct {
|
|
26
|
+
byPath map[string][]nativeRewrite
|
|
27
|
+
aliasesByPath map[string]map[string]bool
|
|
28
|
+
sortedAliasesByPath map[string][]string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func newNativeRewriteSet() *nativeRewriteSet {
|
|
32
|
+
return &nativeRewriteSet{
|
|
33
|
+
byPath: map[string][]nativeRewrite{},
|
|
34
|
+
aliasesByPath: map[string]map[string]bool{},
|
|
35
|
+
sortedAliasesByPath: map[string][]string{},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func (rs *nativeRewriteSet) Add(r nativeRewrite) {
|
|
40
|
+
if r.FilePath == "" {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
path := filepath.ToSlash(r.FilePath)
|
|
44
|
+
rs.byPath[path] = append(rs.byPath[path], r)
|
|
45
|
+
rs.addRuntimeAliases(path, r.Replacement)
|
|
46
|
+
for _, argument := range r.AppendArguments {
|
|
47
|
+
rs.addRuntimeAliases(path, argument)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func (rs *nativeRewriteSet) addRuntimeAliases(path string, text string) {
|
|
52
|
+
for _, alias := range collectCleanupRuntimeAliases(text) {
|
|
53
|
+
if rs.aliasesByPath[path] == nil {
|
|
54
|
+
rs.aliasesByPath[path] = map[string]bool{}
|
|
55
|
+
}
|
|
56
|
+
rs.aliasesByPath[path][alias] = true
|
|
57
|
+
delete(rs.sortedAliasesByPath, path)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func (rs *nativeRewriteSet) Len() int {
|
|
62
|
+
if rs == nil {
|
|
63
|
+
return 0
|
|
64
|
+
}
|
|
65
|
+
n := 0
|
|
66
|
+
for _, rewrites := range rs.byPath {
|
|
67
|
+
n += len(rewrites)
|
|
68
|
+
}
|
|
69
|
+
return n
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func (rs *nativeRewriteSet) Apply(outputName string, text string, cursors map[string]int) (string, error) {
|
|
73
|
+
if rs == nil || len(rs.byPath) == 0 {
|
|
74
|
+
return text, nil
|
|
75
|
+
}
|
|
76
|
+
if strings.Contains(text, "/* @ttsc-rewritten */") {
|
|
77
|
+
return text, nil
|
|
78
|
+
}
|
|
79
|
+
srcPath, ok := rs.findSourceForOutput(outputName)
|
|
80
|
+
if !ok || len(rs.byPath[srcPath]) == 0 {
|
|
81
|
+
return text, nil
|
|
82
|
+
}
|
|
83
|
+
rewrites := rs.byPath[srcPath]
|
|
84
|
+
sort.SliceStable(rewrites, func(i, j int) bool {
|
|
85
|
+
left, leftOK := nativeRewriteFirstIndex(text, rewrites[i])
|
|
86
|
+
right, rightOK := nativeRewriteFirstIndex(text, rewrites[j])
|
|
87
|
+
if leftOK && rightOK && left != right {
|
|
88
|
+
return left < right
|
|
89
|
+
}
|
|
90
|
+
if leftOK != rightOK {
|
|
91
|
+
return leftOK
|
|
92
|
+
}
|
|
93
|
+
return rewrites[i].SourceStart < rewrites[j].SourceStart
|
|
94
|
+
})
|
|
95
|
+
pos := cursors[srcPath]
|
|
96
|
+
out := text
|
|
97
|
+
for pos < len(rewrites) {
|
|
98
|
+
rewrite := rewrites[pos]
|
|
99
|
+
replaced, ok, err := spliceNativeCall(out, rewrite)
|
|
100
|
+
if err != nil {
|
|
101
|
+
return "", err
|
|
102
|
+
}
|
|
103
|
+
if !ok {
|
|
104
|
+
preview := out
|
|
105
|
+
if len(preview) > 400 {
|
|
106
|
+
preview = preview[:400] + "..."
|
|
107
|
+
}
|
|
108
|
+
return "", fmt.Errorf(
|
|
109
|
+
"native rewrite: could not locate %s.%s(...) call in %s (tried roots %v; preview: %q)",
|
|
110
|
+
joinNativeRootAndNamespaces(rewrite),
|
|
111
|
+
rewrite.Method,
|
|
112
|
+
outputName,
|
|
113
|
+
candidateNativeRoots(rewrite.RootName),
|
|
114
|
+
preview,
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
out = replaced
|
|
118
|
+
pos++
|
|
119
|
+
}
|
|
120
|
+
cursors[srcPath] = pos
|
|
121
|
+
if out != text {
|
|
122
|
+
out = insertNativeRewriteSentinel(out)
|
|
123
|
+
}
|
|
124
|
+
return out, nil
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func (rs *nativeRewriteSet) RuntimeAliasesForOutput(outputName string) []string {
|
|
128
|
+
if rs == nil || len(rs.aliasesByPath) == 0 || isJavaScriptOutput(outputName) == false {
|
|
129
|
+
return nil
|
|
130
|
+
}
|
|
131
|
+
srcPath, ok := rs.findSourceForOutput(outputName)
|
|
132
|
+
if !ok {
|
|
133
|
+
return nil
|
|
134
|
+
}
|
|
135
|
+
if aliases, ok := rs.sortedAliasesByPath[srcPath]; ok {
|
|
136
|
+
return aliases
|
|
137
|
+
}
|
|
138
|
+
seen := rs.aliasesByPath[srcPath]
|
|
139
|
+
if len(seen) == 0 {
|
|
140
|
+
rs.sortedAliasesByPath[srcPath] = []string{}
|
|
141
|
+
return rs.sortedAliasesByPath[srcPath]
|
|
142
|
+
}
|
|
143
|
+
aliases := sortCleanupRuntimeAliases(seen)
|
|
144
|
+
rs.sortedAliasesByPath[srcPath] = aliases
|
|
145
|
+
return aliases
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
func nativeRewriteFirstIndex(text string, rewrite nativeRewrite) (int, bool) {
|
|
149
|
+
best := -1
|
|
150
|
+
for _, target := range candidateNativeTargets(rewrite) {
|
|
151
|
+
hit, ok := indexNativeFlexibleCall(text, target, rewrite, 0)
|
|
152
|
+
if ok == false {
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
if best < 0 || hit.start < best {
|
|
156
|
+
best = hit.start
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return best, best >= 0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func (rs *nativeRewriteSet) findSourceForOutput(outputName string) (string, bool) {
|
|
163
|
+
outSlash := strings.TrimSuffix(filepath.ToSlash(outputName), filepath.Ext(outputName))
|
|
164
|
+
for path := range rs.byPath {
|
|
165
|
+
srcStem := strings.TrimSuffix(filepath.ToSlash(path), filepath.Ext(path))
|
|
166
|
+
if OutputMatchesSourceStem(outSlash, srcStem) {
|
|
167
|
+
return path, true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return rs.findSourceByUniqueBase(outSlash)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func (rs *nativeRewriteSet) findSourceByUniqueBase(outputStem string) (string, bool) {
|
|
174
|
+
// Only fall back to basename matching when the output looks like an actual
|
|
175
|
+
// build product (sits inside lib/dist/bin/build). If the output lives in
|
|
176
|
+
// the source tree (e.g. ttsc's virtual filesystem mirroring src/), basename
|
|
177
|
+
// matching crosses unrelated files — `src/index.ts` would silently absorb
|
|
178
|
+
// the rewrite intended for every `src/.../index.ts` sibling.
|
|
179
|
+
if outputHasBuildMarker(outputStem) == false {
|
|
180
|
+
return "", false
|
|
181
|
+
}
|
|
182
|
+
base := pathBase(outputStem)
|
|
183
|
+
if base == "" {
|
|
184
|
+
return "", false
|
|
185
|
+
}
|
|
186
|
+
matched := ""
|
|
187
|
+
count := 0
|
|
188
|
+
for path := range rs.byPath {
|
|
189
|
+
srcStem := strings.TrimSuffix(filepath.ToSlash(path), filepath.Ext(path))
|
|
190
|
+
if pathBase(srcStem) != base {
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
matched = path
|
|
194
|
+
count++
|
|
195
|
+
if count > 1 {
|
|
196
|
+
return "", false
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return matched, count == 1
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func outputHasBuildMarker(stem string) bool {
|
|
203
|
+
for _, marker := range []string{"lib", "dist", "bin", "build"} {
|
|
204
|
+
if _, ok := suffixAfterPathMarker(stem, []string{marker}, false); ok {
|
|
205
|
+
return true
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return false
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func OutputMatchesSourceStem(outputStem string, sourceStem string) bool {
|
|
212
|
+
if outputStem == sourceStem {
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
for _, sourceRel := range sourceOutputCandidates(sourceStem) {
|
|
216
|
+
for _, outputRel := range outputSourceCandidates(outputStem) {
|
|
217
|
+
if sourceRel == outputRel {
|
|
218
|
+
return true
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Suffix-only matching is a fallback for paths that don't share a
|
|
222
|
+
// recognized build marker (e.g. ttsc's virtual filesystem mirroring
|
|
223
|
+
// the source tree). Require the relative path to span at least two
|
|
224
|
+
// segments so a top-level source like `src/index.ts` does not match
|
|
225
|
+
// every output file that happens to end in `/index.js`.
|
|
226
|
+
if strings.Contains(sourceRel, "/") && strings.HasSuffix(outputStem, "/"+sourceRel) {
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return false
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
func pathBase(stem string) string {
|
|
234
|
+
stem = strings.TrimSuffix(filepath.ToSlash(stem), "/")
|
|
235
|
+
if stem == "" {
|
|
236
|
+
return ""
|
|
237
|
+
}
|
|
238
|
+
if idx := strings.LastIndexByte(stem, '/'); idx >= 0 {
|
|
239
|
+
return stem[idx+1:]
|
|
240
|
+
}
|
|
241
|
+
return stem
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func isJavaScriptOutput(fileName string) bool {
|
|
245
|
+
switch strings.ToLower(filepath.Ext(fileName)) {
|
|
246
|
+
case ".js", ".mjs", ".cjs":
|
|
247
|
+
return true
|
|
248
|
+
default:
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func sourceOutputCandidates(stem string) []string {
|
|
254
|
+
candidates := []string{}
|
|
255
|
+
if rel, ok := suffixAfterPathMarker(stem, []string{"src", "api"}, false); ok {
|
|
256
|
+
candidates = append(candidates, rel)
|
|
257
|
+
}
|
|
258
|
+
if rel, ok := suffixAfterPathMarker(stem, []string{"src"}, false); ok {
|
|
259
|
+
candidates = append(candidates, rel)
|
|
260
|
+
}
|
|
261
|
+
if rel, ok := suffixAfterPathMarker(stem, []string{"test"}, true); ok {
|
|
262
|
+
candidates = append(candidates, rel)
|
|
263
|
+
}
|
|
264
|
+
return candidates
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
func outputSourceCandidates(stem string) []string {
|
|
268
|
+
candidates := []string{}
|
|
269
|
+
// Mirror sourceOutputCandidates so paths whose source and output trees
|
|
270
|
+
// share the same `src/` (or `test/`) root — e.g. ttsc's virtual filesystem
|
|
271
|
+
// emits next to the source — can intersect on the same relative tail.
|
|
272
|
+
// We intentionally do NOT include the {"src","api"} marker here: that
|
|
273
|
+
// marker exists on the source side to let a source under `src/api/`
|
|
274
|
+
// pretend its rel is the leaf only (so it matches an output that lacks
|
|
275
|
+
// the `api/` segment); mirroring it on the output side would let two
|
|
276
|
+
// unrelated files (e.g. `src/index.ts` and `src/api/index.ts`) collide
|
|
277
|
+
// on the rel "index".
|
|
278
|
+
for _, marker := range [][]string{{"lib"}, {"bin"}, {"dist"}, {"build"}, {"src"}} {
|
|
279
|
+
if rel, ok := suffixAfterPathMarker(stem, marker, false); ok {
|
|
280
|
+
candidates = append(candidates, rel)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if rel, ok := suffixAfterPathMarker(stem, []string{"test"}, true); ok {
|
|
284
|
+
candidates = append(candidates, rel)
|
|
285
|
+
}
|
|
286
|
+
return append(candidates, stem)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func suffixAfterPathMarker(stem string, marker []string, includeMarker bool) (string, bool) {
|
|
290
|
+
segments := strings.Split(filepath.ToSlash(stem), "/")
|
|
291
|
+
for i := len(segments) - len(marker); i >= 0; i-- {
|
|
292
|
+
matched := true
|
|
293
|
+
for j := range marker {
|
|
294
|
+
if segments[i+j] != marker[j] {
|
|
295
|
+
matched = false
|
|
296
|
+
break
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if !matched {
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
start := i + len(marker)
|
|
303
|
+
if includeMarker {
|
|
304
|
+
start = i
|
|
305
|
+
}
|
|
306
|
+
if start >= len(segments) {
|
|
307
|
+
return "", false
|
|
308
|
+
}
|
|
309
|
+
return strings.Join(segments[start:], "/"), true
|
|
310
|
+
}
|
|
311
|
+
return "", false
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
func spliceNativeCall(text string, r nativeRewrite) (string, bool, error) {
|
|
315
|
+
for _, target := range candidateNativeTargets(r) {
|
|
316
|
+
searchFrom := 0
|
|
317
|
+
for {
|
|
318
|
+
hit, ok := indexNativeFlexibleCall(text, target, r, searchFrom)
|
|
319
|
+
if !ok {
|
|
320
|
+
break
|
|
321
|
+
}
|
|
322
|
+
closePos, ok := matchNativeParen(text, hit.paren)
|
|
323
|
+
if !ok {
|
|
324
|
+
searchFrom = advanceNativeSearch(searchFrom, hit)
|
|
325
|
+
continue
|
|
326
|
+
}
|
|
327
|
+
if r.ExpectedArgumentCount != nil &&
|
|
328
|
+
countNativeArguments(text[hit.paren+1:closePos]) != *r.ExpectedArgumentCount {
|
|
329
|
+
searchFrom = advanceNativeSearch(searchFrom, hit)
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
current := strings.TrimSpace(text[hit.paren+1 : closePos])
|
|
333
|
+
if r.ExpectedArgumentsText != "" && compactNativeArgumentText(current) != compactNativeArgumentText(r.ExpectedArgumentsText) {
|
|
334
|
+
searchFrom = advanceNativeSearch(searchFrom, hit)
|
|
335
|
+
continue
|
|
336
|
+
}
|
|
337
|
+
if len(r.AppendArguments) != 0 {
|
|
338
|
+
next := strings.Join(r.AppendArguments, ", ")
|
|
339
|
+
if current != "" {
|
|
340
|
+
next = current + ", " + next
|
|
341
|
+
}
|
|
342
|
+
replaced := text[:hit.paren+1] + next + text[closePos:]
|
|
343
|
+
return replaced, true, nil
|
|
344
|
+
}
|
|
345
|
+
if r.ConsumeParens {
|
|
346
|
+
replaced := text[:hit.start] + r.Replacement + text[closePos+1:]
|
|
347
|
+
return replaced, true, nil
|
|
348
|
+
}
|
|
349
|
+
replaced := text[:hit.start] + r.Replacement + text[hit.paren:]
|
|
350
|
+
return replaced, true, nil
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return text, false, nil
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
func advanceNativeSearch(current int, hit nativeCallHit) int {
|
|
357
|
+
next := hit.paren + 1
|
|
358
|
+
if next <= current {
|
|
359
|
+
return current + 1
|
|
360
|
+
}
|
|
361
|
+
return next
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
func compactNativeArgumentText(text string) string {
|
|
365
|
+
var builder strings.Builder
|
|
366
|
+
inString := byte(0)
|
|
367
|
+
escaped := false
|
|
368
|
+
for i := 0; i < len(text); i++ {
|
|
369
|
+
ch := text[i]
|
|
370
|
+
if inString != 0 {
|
|
371
|
+
builder.WriteByte(ch)
|
|
372
|
+
if escaped {
|
|
373
|
+
escaped = false
|
|
374
|
+
} else if ch == '\\' {
|
|
375
|
+
escaped = true
|
|
376
|
+
} else if ch == inString {
|
|
377
|
+
inString = 0
|
|
378
|
+
}
|
|
379
|
+
continue
|
|
380
|
+
}
|
|
381
|
+
switch ch {
|
|
382
|
+
case '"', '\'', '`':
|
|
383
|
+
inString = ch
|
|
384
|
+
builder.WriteByte(ch)
|
|
385
|
+
case ' ', '\t', '\r', '\n':
|
|
386
|
+
continue
|
|
387
|
+
default:
|
|
388
|
+
builder.WriteByte(ch)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return builder.String()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
type nativeCallHit struct {
|
|
395
|
+
start int
|
|
396
|
+
paren int
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
func indexNativeFlexibleCall(text string, target string, r nativeRewrite, searchFrom int) (nativeCallHit, bool) {
|
|
400
|
+
parts := strings.Split(target, ".")
|
|
401
|
+
if len(parts) == 0 || parts[0] == "" {
|
|
402
|
+
return nativeCallHit{}, false
|
|
403
|
+
}
|
|
404
|
+
root := parts[0]
|
|
405
|
+
parts = parts[1:]
|
|
406
|
+
start := searchFrom
|
|
407
|
+
if start < 0 {
|
|
408
|
+
start = 0
|
|
409
|
+
}
|
|
410
|
+
for {
|
|
411
|
+
hit := strings.Index(text[start:], root)
|
|
412
|
+
if hit < 0 {
|
|
413
|
+
return nativeCallHit{}, false
|
|
414
|
+
}
|
|
415
|
+
pos := start + hit
|
|
416
|
+
if pos > 0 && isNativeIdentifierPart(rune(text[pos-1])) {
|
|
417
|
+
start = pos + 1
|
|
418
|
+
continue
|
|
419
|
+
}
|
|
420
|
+
cursor := pos + len(root)
|
|
421
|
+
ok := true
|
|
422
|
+
for _, part := range parts {
|
|
423
|
+
cursor = skipNativeWhitespace(text, cursor)
|
|
424
|
+
if cursor >= len(text) || text[cursor] != '.' {
|
|
425
|
+
ok = false
|
|
426
|
+
break
|
|
427
|
+
}
|
|
428
|
+
cursor++
|
|
429
|
+
cursor = skipNativeWhitespace(text, cursor)
|
|
430
|
+
if !strings.HasPrefix(text[cursor:], part) {
|
|
431
|
+
ok = false
|
|
432
|
+
break
|
|
433
|
+
}
|
|
434
|
+
cursor += len(part)
|
|
435
|
+
}
|
|
436
|
+
cursor = skipNativeWhitespace(text, cursor)
|
|
437
|
+
if ok && cursor < len(text) && text[cursor] == '(' {
|
|
438
|
+
return nativeCallHit{start: pos, paren: cursor}, true
|
|
439
|
+
}
|
|
440
|
+
if ok {
|
|
441
|
+
if wrappedStart, paren, wrapped := matchNativeCommaWrappedCall(text, pos, cursor); wrapped {
|
|
442
|
+
return nativeCallHit{start: wrappedStart, paren: paren}, true
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
start = pos + 1
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
func skipNativeWhitespace(text string, pos int) int {
|
|
450
|
+
for pos < len(text) {
|
|
451
|
+
switch text[pos] {
|
|
452
|
+
case ' ', '\t', '\r', '\n':
|
|
453
|
+
pos++
|
|
454
|
+
default:
|
|
455
|
+
return pos
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return pos
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
func countNativeArguments(text string) int {
|
|
462
|
+
text = strings.TrimSpace(text)
|
|
463
|
+
if text == "" {
|
|
464
|
+
return 0
|
|
465
|
+
}
|
|
466
|
+
count := 1
|
|
467
|
+
depth := 0
|
|
468
|
+
// templateDepths records the bracket depth at which each currently-open
|
|
469
|
+
// `${...}` substitution started. When depth drops back to that level on a
|
|
470
|
+
// '}' token, we pop and re-enter template-string mode.
|
|
471
|
+
templateDepths := []int{}
|
|
472
|
+
i := 0
|
|
473
|
+
for i < len(text) {
|
|
474
|
+
ch := text[i]
|
|
475
|
+
switch ch {
|
|
476
|
+
case '(', '[', '{':
|
|
477
|
+
depth++
|
|
478
|
+
case ')', ']':
|
|
479
|
+
if depth > 0 {
|
|
480
|
+
depth--
|
|
481
|
+
}
|
|
482
|
+
case '}':
|
|
483
|
+
if depth > 0 {
|
|
484
|
+
depth--
|
|
485
|
+
}
|
|
486
|
+
if n := len(templateDepths); n > 0 && templateDepths[n-1] == depth {
|
|
487
|
+
templateDepths = templateDepths[:n-1]
|
|
488
|
+
i++
|
|
489
|
+
skipTemplateLiteral(text, &i, &templateDepths, &depth)
|
|
490
|
+
continue
|
|
491
|
+
}
|
|
492
|
+
case '"', '\'':
|
|
493
|
+
i++
|
|
494
|
+
for i < len(text) && text[i] != ch {
|
|
495
|
+
if text[i] == '\\' && i+1 < len(text) {
|
|
496
|
+
i++
|
|
497
|
+
}
|
|
498
|
+
i++
|
|
499
|
+
}
|
|
500
|
+
case '`':
|
|
501
|
+
i++
|
|
502
|
+
skipTemplateLiteral(text, &i, &templateDepths, &depth)
|
|
503
|
+
continue
|
|
504
|
+
case ',':
|
|
505
|
+
if depth == 0 {
|
|
506
|
+
count++
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
i++
|
|
510
|
+
}
|
|
511
|
+
return count
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// skipTemplateLiteral advances i through a template-literal body. It exits
|
|
515
|
+
// either at the matching closing backtick (consuming it) or at the opening
|
|
516
|
+
// of a `${...}` substitution, in which case the caller resumes regular
|
|
517
|
+
// expression parsing and templateDepths records that we owe a return to
|
|
518
|
+
// template-string mode on the matching '}'.
|
|
519
|
+
func skipTemplateLiteral(text string, i *int, templateDepths *[]int, depth *int) {
|
|
520
|
+
for *i < len(text) {
|
|
521
|
+
c := text[*i]
|
|
522
|
+
if c == '\\' && *i+1 < len(text) {
|
|
523
|
+
*i += 2
|
|
524
|
+
continue
|
|
525
|
+
}
|
|
526
|
+
if c == '`' {
|
|
527
|
+
*i++
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
if c == '$' && *i+1 < len(text) && text[*i+1] == '{' {
|
|
531
|
+
*templateDepths = append(*templateDepths, *depth)
|
|
532
|
+
*depth++
|
|
533
|
+
*i += 2
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
*i++
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
func candidateNativeRoots(root string) []string {
|
|
541
|
+
return []string{
|
|
542
|
+
root,
|
|
543
|
+
root + "_1.default",
|
|
544
|
+
root + "_2.default",
|
|
545
|
+
root + ".default",
|
|
546
|
+
root + "_1",
|
|
547
|
+
root + "_2",
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
func candidateNativeTargets(r nativeRewrite) []string {
|
|
552
|
+
seen := map[string]bool{}
|
|
553
|
+
output := []string{}
|
|
554
|
+
add := func(value string) {
|
|
555
|
+
value = strings.TrimSpace(value)
|
|
556
|
+
if value == "" || seen[value] {
|
|
557
|
+
return
|
|
558
|
+
}
|
|
559
|
+
seen[value] = true
|
|
560
|
+
output = append(output, value)
|
|
561
|
+
}
|
|
562
|
+
for _, candidate := range r.TargetExpressionCandidates {
|
|
563
|
+
add(candidate)
|
|
564
|
+
}
|
|
565
|
+
if r.Method == "" && len(r.Namespaces) == 0 {
|
|
566
|
+
for _, root := range candidateNativeRoots(r.RootName) {
|
|
567
|
+
add(root)
|
|
568
|
+
}
|
|
569
|
+
return output
|
|
570
|
+
}
|
|
571
|
+
for _, root := range candidateNativeRoots(r.RootName) {
|
|
572
|
+
parts := []string{root}
|
|
573
|
+
parts = append(parts, r.Namespaces...)
|
|
574
|
+
if r.Method != "" {
|
|
575
|
+
parts = append(parts, r.Method)
|
|
576
|
+
}
|
|
577
|
+
add(strings.Join(parts, "."))
|
|
578
|
+
}
|
|
579
|
+
return output
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
func matchNativeCommaWrappedCall(text string, exprStart int, exprEnd int) (int, int, bool) {
|
|
583
|
+
left := exprStart - 1
|
|
584
|
+
for left >= 0 && (text[left] == ' ' || text[left] == '\t' || text[left] == '\r' || text[left] == '\n') {
|
|
585
|
+
left--
|
|
586
|
+
}
|
|
587
|
+
if left < 0 || text[left] != ',' {
|
|
588
|
+
return 0, 0, false
|
|
589
|
+
}
|
|
590
|
+
left--
|
|
591
|
+
for left >= 0 && (text[left] == ' ' || text[left] == '\t' || text[left] == '\r' || text[left] == '\n') {
|
|
592
|
+
left--
|
|
593
|
+
}
|
|
594
|
+
if left < 0 || text[left] != '0' {
|
|
595
|
+
return 0, 0, false
|
|
596
|
+
}
|
|
597
|
+
left--
|
|
598
|
+
for left >= 0 && (text[left] == ' ' || text[left] == '\t' || text[left] == '\r' || text[left] == '\n') {
|
|
599
|
+
left--
|
|
600
|
+
}
|
|
601
|
+
if left < 0 || text[left] != '(' {
|
|
602
|
+
return 0, 0, false
|
|
603
|
+
}
|
|
604
|
+
right := skipNativeWhitespace(text, exprEnd)
|
|
605
|
+
if right >= len(text) || text[right] != ')' {
|
|
606
|
+
return 0, 0, false
|
|
607
|
+
}
|
|
608
|
+
paren := skipNativeWhitespace(text, right+1)
|
|
609
|
+
if paren >= len(text) || text[paren] != '(' {
|
|
610
|
+
return 0, 0, false
|
|
611
|
+
}
|
|
612
|
+
return left, paren, true
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
func joinNativeRootAndNamespaces(r nativeRewrite) string {
|
|
616
|
+
if len(r.Namespaces) == 0 {
|
|
617
|
+
return r.RootName
|
|
618
|
+
}
|
|
619
|
+
return r.RootName + "." + strings.Join(r.Namespaces, ".")
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
func matchNativeParen(text string, pos int) (int, bool) {
|
|
623
|
+
if pos >= len(text) || text[pos] != '(' {
|
|
624
|
+
return 0, false
|
|
625
|
+
}
|
|
626
|
+
depth := 1
|
|
627
|
+
for i := pos + 1; i < len(text); i++ {
|
|
628
|
+
switch text[i] {
|
|
629
|
+
case '(':
|
|
630
|
+
depth++
|
|
631
|
+
case ')':
|
|
632
|
+
depth--
|
|
633
|
+
if depth == 0 {
|
|
634
|
+
return i, true
|
|
635
|
+
}
|
|
636
|
+
case '"', '\'', '`':
|
|
637
|
+
q := text[i]
|
|
638
|
+
j := i + 1
|
|
639
|
+
for j < len(text) && text[j] != q {
|
|
640
|
+
if text[j] == '\\' {
|
|
641
|
+
j++
|
|
642
|
+
}
|
|
643
|
+
j++
|
|
644
|
+
}
|
|
645
|
+
i = j
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return 0, false
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
func isNativeIdentifierPart(r rune) bool {
|
|
652
|
+
return r == '_' || r == '$' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
func insertNativeRewriteSentinel(text string) string {
|
|
656
|
+
for _, prefix := range []string{"\"use strict\";\n", "'use strict';\n"} {
|
|
657
|
+
if strings.HasPrefix(text, prefix) {
|
|
658
|
+
return prefix + "/* @ttsc-rewritten */\n" + text[len(prefix):]
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return "/* @ttsc-rewritten */\n" + text
|
|
662
|
+
}
|