@zyno-io/ts-reflection 26.803.2224
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/README.md +13 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +966 -0
- package/dist/reflection/annotations.d.ts +15 -0
- package/dist/reflection/annotations.d.ts.map +1 -0
- package/dist/reflection/compact-metadata.d.ts +18 -0
- package/dist/reflection/compact-metadata.d.ts.map +1 -0
- package/dist/reflection/conversion.d.ts +15 -0
- package/dist/reflection/conversion.d.ts.map +1 -0
- package/dist/reflection/deserializer.d.ts +15 -0
- package/dist/reflection/deserializer.d.ts.map +1 -0
- package/dist/reflection/errors.d.ts +8 -0
- package/dist/reflection/errors.d.ts.map +1 -0
- package/dist/reflection/index.d.ts +10 -0
- package/dist/reflection/index.d.ts.map +1 -0
- package/dist/reflection/metadata-store.d.ts +20 -0
- package/dist/reflection/metadata-store.d.ts.map +1 -0
- package/dist/reflection/model.d.ts +273 -0
- package/dist/reflection/model.d.ts.map +1 -0
- package/dist/reflection/primitive-conversion.d.ts +2 -0
- package/dist/reflection/primitive-conversion.d.ts.map +1 -0
- package/dist/reflection/reflection-class.d.ts +60 -0
- package/dist/reflection/reflection-class.d.ts.map +1 -0
- package/dist/reflection/type-utils.d.ts +34 -0
- package/dist/reflection/type-utils.d.ts.map +1 -0
- package/dist/type-compiler/download-prebuilt.cjs +114 -0
- package/dist/type-compiler/go/ast_expression.go +187 -0
- package/dist/type-compiler/go/ast_metadata.go +388 -0
- package/dist/type-compiler/go/collect.go +963 -0
- package/dist/type-compiler/go/compact_metadata.go +553 -0
- package/dist/type-compiler/go/emission_plan.go +340 -0
- package/dist/type-compiler/go/emit_ast.go +557 -0
- package/dist/type-compiler/go/emit_ast_test.go +558 -0
- package/dist/type-compiler/go/go.mod +10 -0
- package/dist/type-compiler/go/plugin.go +359 -0
- package/dist/type-compiler/go/plugin_test.go +1206 -0
- package/dist/type-compiler/go/precompute.go +86 -0
- package/dist/type-compiler/go/receive_type.go +912 -0
- package/dist/type-compiler/go/resolve.go +265 -0
- package/dist/type-compiler/go/source_scan.go +51 -0
- package/dist/type-compiler/go/text_parse.go +734 -0
- package/dist/type-compiler/go/type_expr.go +1291 -0
- package/dist/type-compiler/go/typia_expr.go +2316 -0
- package/dist/type-compiler/index.cjs +43 -0
- package/dist/type-compiler/pnp.cjs +474 -0
- package/dist/type-compiler/prebuilt.cjs +324 -0
- package/dist/type-metadata-runtime.cjs +1 -0
- package/dist/type-metadata-runtime.d.ts +2 -0
- package/dist/type-metadata-runtime.d.ts.map +1 -0
- package/dist/type-metadata-runtime.js +107 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/primitives.d.ts +33 -0
- package/dist/types/primitives.d.ts.map +1 -0
- package/dist/types/runtime.d.ts +2 -0
- package/dist/types/runtime.d.ts.map +1 -0
- package/dist/types/type-annotations.d.ts +28 -0
- package/dist/types/type-annotations.d.ts.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"io/fs"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"regexp"
|
|
8
|
+
"sort"
|
|
9
|
+
"strconv"
|
|
10
|
+
"strings"
|
|
11
|
+
|
|
12
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type sourceCallInfo struct {
|
|
16
|
+
name string
|
|
17
|
+
receiver string
|
|
18
|
+
typeArgs []string
|
|
19
|
+
args []string
|
|
20
|
+
pos int
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func resolvedReceiveTypeCall(info *fileInfo, reg *registry, node *shimast.Node) (callInfo, bool) {
|
|
24
|
+
if info == nil || info.file == nil || reg == nil || reg.checker == nil || node == nil || node.Kind != shimast.KindCallExpression {
|
|
25
|
+
return callInfo{}, false
|
|
26
|
+
}
|
|
27
|
+
call := node.AsCallExpression()
|
|
28
|
+
signature := reg.checker.GetResolvedSignature(node)
|
|
29
|
+
if signature == nil {
|
|
30
|
+
return callInfo{}, false
|
|
31
|
+
}
|
|
32
|
+
declaration := signature.Declaration()
|
|
33
|
+
if declaration == nil {
|
|
34
|
+
return callInfo{}, false
|
|
35
|
+
}
|
|
36
|
+
declarationFile := shimast.GetSourceFileOfNode(declaration)
|
|
37
|
+
if declarationFile == nil {
|
|
38
|
+
return callInfo{}, false
|
|
39
|
+
}
|
|
40
|
+
name := "ReceiveType"
|
|
41
|
+
if declaration.Name() != nil {
|
|
42
|
+
// A resolved signature can be declared by a computed method, whose name
|
|
43
|
+
// node cannot be converted to text by the TypeScript Go AST API.
|
|
44
|
+
name = nodeText(declarationFile, declaration.Name().AsNode())
|
|
45
|
+
}
|
|
46
|
+
fn := functionInfo{
|
|
47
|
+
name: name,
|
|
48
|
+
typeParams: typeParameterNames(declaration),
|
|
49
|
+
params: paramsFromNode(declarationFile, declaration),
|
|
50
|
+
pos: declaration.Pos(),
|
|
51
|
+
}
|
|
52
|
+
receiveTypeText := ""
|
|
53
|
+
if _, text, ok := receiveTypeParameter(fn); ok {
|
|
54
|
+
receiveTypeText = unwrapReceiveTypeHelperType(text)
|
|
55
|
+
} else {
|
|
56
|
+
return callInfo{}, false
|
|
57
|
+
}
|
|
58
|
+
sourceCall := sourceCallInfo{name: name, pos: node.Pos()}
|
|
59
|
+
for _, typeArg := range node.TypeArguments() {
|
|
60
|
+
sourceCall.typeArgs = append(sourceCall.typeArgs, nodeText(info.file, typeArg))
|
|
61
|
+
}
|
|
62
|
+
if call.Arguments != nil {
|
|
63
|
+
for _, arg := range call.Arguments.Nodes {
|
|
64
|
+
sourceCall.args = append(sourceCall.args, nodeText(info.file, arg))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
typeText, metadataArgIndex, ok := receiveTypeForCall(info, reg, fn, sourceCall)
|
|
68
|
+
if !ok {
|
|
69
|
+
return callInfo{}, false
|
|
70
|
+
}
|
|
71
|
+
var typeNode *shimast.Node
|
|
72
|
+
for index, typeParam := range fn.typeParams {
|
|
73
|
+
if receiveTypeText == typeParam && index < len(node.TypeArguments()) {
|
|
74
|
+
typeNode = node.TypeArguments()[index]
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return callInfo{
|
|
79
|
+
name: name,
|
|
80
|
+
nodePos: node.Pos(),
|
|
81
|
+
metadataArgIndex: metadataArgIndex,
|
|
82
|
+
typeText: typeText,
|
|
83
|
+
typeNode: typeNode,
|
|
84
|
+
preferTypia: typeNode != nil,
|
|
85
|
+
pos: node.Pos(),
|
|
86
|
+
}, true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func collectReceiveTypeCalls(info *fileInfo, reg *registry) []callInfo {
|
|
90
|
+
text := info.file.Text()
|
|
91
|
+
out := []callInfo{}
|
|
92
|
+
seen := map[int]bool{}
|
|
93
|
+
for name, fns := range receiveTypeFunctionCandidates(info, reg) {
|
|
94
|
+
if len(fns) == 0 {
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
for _, call := range sourceCalls(text, name) {
|
|
98
|
+
if seen[call.pos] {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
for _, fn := range fns {
|
|
102
|
+
typeText, metadataArgIndex, ok := receiveTypeForCall(info, reg, fn, call)
|
|
103
|
+
if !ok {
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
out = append(out, callInfo{name: name, nodePos: -1, metadataArgIndex: metadataArgIndex, typeText: typeText, pos: call.pos})
|
|
107
|
+
seen[call.pos] = true
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for name, fns := range receiveTypeMethodCandidates(reg) {
|
|
113
|
+
if len(fns) == 0 {
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
for _, call := range sourceMethodCalls(text, name) {
|
|
117
|
+
if seen[call.pos] {
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
typeText, metadataArgIndex, ok := receiveTypeMethodForCall(info, reg, fns, call)
|
|
121
|
+
if ok {
|
|
122
|
+
out = append(out, callInfo{name: name, nodePos: -1, metadataArgIndex: metadataArgIndex, typeText: typeText, pos: call.pos})
|
|
123
|
+
seen[call.pos] = true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return out
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func receiveTypeFunctionCandidates(info *fileInfo, reg *registry) map[string][]functionInfo {
|
|
131
|
+
out := map[string][]functionInfo{}
|
|
132
|
+
for name, fns := range info.functions {
|
|
133
|
+
if receive := receiveTypeFunctionInfos(fns); len(receive) > 0 {
|
|
134
|
+
out[name] = append(out[name], receive...)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for localName, ref := range info.imports {
|
|
138
|
+
target := reg.byPath[ref.source]
|
|
139
|
+
if target != nil {
|
|
140
|
+
fns := target.functions[ref.exportName]
|
|
141
|
+
if receive := receiveTypeFunctionInfos(fns); len(receive) > 0 {
|
|
142
|
+
out[localName] = append(out[localName], receive...)
|
|
143
|
+
}
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
if receive := externalReceiveTypeFunctionInfos(info.file.FileName(), ref.spec, ref.exportName, reg); len(receive) > 0 {
|
|
147
|
+
out[localName] = append(out[localName], receive...)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func receiveTypeFunctionInfos(fns []functionInfo) []functionInfo {
|
|
154
|
+
out := []functionInfo{}
|
|
155
|
+
for _, fn := range fns {
|
|
156
|
+
if _, _, ok := receiveTypeParameter(fn); ok {
|
|
157
|
+
out = append(out, fn)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return out
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
func externalReceiveTypeFunctionInfos(fromFile string, spec string, exportName string, reg *registry) []functionInfo {
|
|
164
|
+
functions := externalFunctionInfos(fromFile, spec, reg)
|
|
165
|
+
if len(functions) == 0 {
|
|
166
|
+
return nil
|
|
167
|
+
}
|
|
168
|
+
return receiveTypeFunctionInfos(functions[exportName])
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func externalFunctionInfos(fromFile string, spec string, reg *registry) map[string][]functionInfo {
|
|
172
|
+
root := externalPackageRoot(fromFile, spec, reg)
|
|
173
|
+
if root == "" {
|
|
174
|
+
return nil
|
|
175
|
+
}
|
|
176
|
+
if cached, ok := reg.external[root]; ok {
|
|
177
|
+
return cached
|
|
178
|
+
}
|
|
179
|
+
functions := map[string][]functionInfo{}
|
|
180
|
+
_ = filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
|
|
181
|
+
if err != nil {
|
|
182
|
+
return nil
|
|
183
|
+
}
|
|
184
|
+
name := entry.Name()
|
|
185
|
+
if entry.IsDir() {
|
|
186
|
+
switch name {
|
|
187
|
+
case ".git", ".yarn", "node_modules", "coverage":
|
|
188
|
+
return filepath.SkipDir
|
|
189
|
+
}
|
|
190
|
+
return nil
|
|
191
|
+
}
|
|
192
|
+
slash := filepath.ToSlash(path)
|
|
193
|
+
if !(strings.HasSuffix(slash, ".ts") || strings.HasSuffix(slash, ".d.ts")) || strings.HasSuffix(slash, ".js") {
|
|
194
|
+
return nil
|
|
195
|
+
}
|
|
196
|
+
textBytes, err := os.ReadFile(path)
|
|
197
|
+
if err != nil {
|
|
198
|
+
return nil
|
|
199
|
+
}
|
|
200
|
+
for _, fn := range exportedFunctionsFromText(string(textBytes)) {
|
|
201
|
+
functions[fn.name] = append(functions[fn.name], fn)
|
|
202
|
+
}
|
|
203
|
+
return nil
|
|
204
|
+
})
|
|
205
|
+
reg.external[root] = functions
|
|
206
|
+
return functions
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
func externalPackageRoot(fromFile string, spec string, reg *registry) string {
|
|
210
|
+
if strings.HasPrefix(spec, ".") || strings.HasPrefix(spec, "/") {
|
|
211
|
+
return ""
|
|
212
|
+
}
|
|
213
|
+
pkg := packageNameFromSpec(spec)
|
|
214
|
+
if pkg == "" {
|
|
215
|
+
return ""
|
|
216
|
+
}
|
|
217
|
+
if reg != nil {
|
|
218
|
+
if root := reg.externalPackageRoots[pkg]; root != "" {
|
|
219
|
+
if stat, err := os.Stat(root); err == nil && stat.IsDir() {
|
|
220
|
+
return filepath.Clean(root)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
dir := filepath.Dir(fromFile)
|
|
225
|
+
for {
|
|
226
|
+
candidate := filepath.Join(dir, "node_modules", filepath.FromSlash(pkg))
|
|
227
|
+
if stat, err := os.Stat(candidate); err == nil && stat.IsDir() {
|
|
228
|
+
if real, err := filepath.EvalSymlinks(candidate); err == nil {
|
|
229
|
+
return filepath.Clean(real)
|
|
230
|
+
}
|
|
231
|
+
return filepath.Clean(candidate)
|
|
232
|
+
}
|
|
233
|
+
parent := filepath.Dir(dir)
|
|
234
|
+
if parent == dir {
|
|
235
|
+
return ""
|
|
236
|
+
}
|
|
237
|
+
dir = parent
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
func packageNameFromSpec(spec string) string {
|
|
242
|
+
parts := strings.Split(filepath.ToSlash(spec), "/")
|
|
243
|
+
if len(parts) == 0 || parts[0] == "" {
|
|
244
|
+
return ""
|
|
245
|
+
}
|
|
246
|
+
if strings.HasPrefix(parts[0], "@") {
|
|
247
|
+
if len(parts) < 2 || parts[1] == "" {
|
|
248
|
+
return ""
|
|
249
|
+
}
|
|
250
|
+
return parts[0] + "/" + parts[1]
|
|
251
|
+
}
|
|
252
|
+
return parts[0]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func exportedFunctionsFromText(text string) []functionInfo {
|
|
256
|
+
out := []functionInfo{}
|
|
257
|
+
search := 0
|
|
258
|
+
for search < len(text) {
|
|
259
|
+
idx := strings.Index(text[search:], "function")
|
|
260
|
+
if idx < 0 {
|
|
261
|
+
break
|
|
262
|
+
}
|
|
263
|
+
start := search + idx
|
|
264
|
+
after := start + len("function")
|
|
265
|
+
if start > 0 && isIdent(text[start-1]) || after < len(text) && isIdent(text[after]) {
|
|
266
|
+
search = after
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
if !isExportedFunctionPosition(text, start) {
|
|
270
|
+
search = after
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
pos := skipSpace(text, after)
|
|
274
|
+
nameStart, ok := scanIdentifierRight(text, pos)
|
|
275
|
+
if !ok {
|
|
276
|
+
search = after
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
name := text[pos:nameStart]
|
|
280
|
+
pos = skipSpace(text, nameStart)
|
|
281
|
+
typeParams := []string{}
|
|
282
|
+
if pos < len(text) && text[pos] == '<' {
|
|
283
|
+
end := findBalanced(text, pos, '<', '>')
|
|
284
|
+
if end < 0 {
|
|
285
|
+
search = pos + 1
|
|
286
|
+
continue
|
|
287
|
+
}
|
|
288
|
+
typeParams = typeParameterNamesFromText(text[pos+1 : end])
|
|
289
|
+
pos = skipSpace(text, end+1)
|
|
290
|
+
}
|
|
291
|
+
if pos >= len(text) || text[pos] != '(' {
|
|
292
|
+
search = nameStart
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
close := findBalanced(text, pos, '(', ')')
|
|
296
|
+
if close < 0 {
|
|
297
|
+
search = pos + 1
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
out = append(out, functionInfo{name: name, typeParams: typeParams, params: paramsFromText(text[pos+1 : close]), pos: start})
|
|
301
|
+
search = close + 1
|
|
302
|
+
}
|
|
303
|
+
return out
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
func isExportedFunctionPosition(text string, start int) bool {
|
|
307
|
+
lineStart := strings.LastIndexAny(text[:start], "\r\n")
|
|
308
|
+
if lineStart < 0 {
|
|
309
|
+
lineStart = 0
|
|
310
|
+
} else {
|
|
311
|
+
lineStart++
|
|
312
|
+
}
|
|
313
|
+
prefix := strings.TrimSpace(text[lineStart:start])
|
|
314
|
+
return regexp.MustCompile(`(?:^|\b)export(?:\s+declare)?\s*$`).MatchString(prefix)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
func scanIdentifierRight(text string, pos int) (int, bool) {
|
|
318
|
+
if pos >= len(text) || !isIdent(text[pos]) || text[pos] >= '0' && text[pos] <= '9' {
|
|
319
|
+
return 0, false
|
|
320
|
+
}
|
|
321
|
+
for pos < len(text) && isIdent(text[pos]) {
|
|
322
|
+
pos++
|
|
323
|
+
}
|
|
324
|
+
return pos, true
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
func typeParameterNamesFromText(raw string) []string {
|
|
328
|
+
params := []string{}
|
|
329
|
+
for _, part := range splitTop(raw, ",") {
|
|
330
|
+
part = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(part), "const "))
|
|
331
|
+
if part == "" {
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
name := part
|
|
335
|
+
for _, marker := range []string{" extends ", " = ", " "} {
|
|
336
|
+
if idx := strings.Index(name, marker); idx >= 0 {
|
|
337
|
+
name = strings.TrimSpace(name[:idx])
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if isIdentifierName(name) {
|
|
341
|
+
params = append(params, name)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return params
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
func paramsFromText(raw string) []paramInfo {
|
|
348
|
+
params := []paramInfo{}
|
|
349
|
+
for _, part := range splitTop(raw, ",") {
|
|
350
|
+
part = strings.TrimSpace(part)
|
|
351
|
+
if part == "" {
|
|
352
|
+
continue
|
|
353
|
+
}
|
|
354
|
+
colon := topLevelColon(part)
|
|
355
|
+
if colon < 0 {
|
|
356
|
+
continue
|
|
357
|
+
}
|
|
358
|
+
name := strings.TrimSpace(strings.TrimPrefix(part[:colon], "..."))
|
|
359
|
+
optional := strings.HasSuffix(name, "?")
|
|
360
|
+
name = strings.TrimSuffix(name, "?")
|
|
361
|
+
typeText := strings.TrimSpace(part[colon+1:])
|
|
362
|
+
hasDefault := false
|
|
363
|
+
if eq := topLevelEquals(typeText); eq >= 0 {
|
|
364
|
+
typeText = strings.TrimSpace(typeText[:eq])
|
|
365
|
+
hasDefault = true
|
|
366
|
+
}
|
|
367
|
+
params = append(params, paramInfo{name: name, typeText: typeText, optional: optional || hasDefault, hasDefault: hasDefault})
|
|
368
|
+
}
|
|
369
|
+
return params
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
func receiveTypeMethodCandidates(reg *registry) map[string][]functionInfo {
|
|
373
|
+
out := map[string][]functionInfo{}
|
|
374
|
+
for _, info := range reg.files {
|
|
375
|
+
for _, class := range info.classes {
|
|
376
|
+
methods := append(append([]methodInfo(nil), class.methods...), class.staticMethods...)
|
|
377
|
+
for _, method := range methods {
|
|
378
|
+
fn := functionInfo{name: method.name, owner: class.name, typeParams: method.typeParams, params: method.params, pos: class.pos}
|
|
379
|
+
if _, _, ok := receiveTypeParameter(fn); ok {
|
|
380
|
+
out[method.name] = append(out[method.name], fn)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return out
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
func receiveTypeParameter(fn functionInfo) (int, string, bool) {
|
|
389
|
+
if len(fn.params) == 0 {
|
|
390
|
+
return 0, "", false
|
|
391
|
+
}
|
|
392
|
+
index := len(fn.params) - 1
|
|
393
|
+
typeText, ok := receiveTypeArgument(fn.params[index].typeText)
|
|
394
|
+
return index, typeText, ok
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
func receiveTypeArgument(raw string) (string, bool) {
|
|
398
|
+
raw = strings.TrimSpace(trimParens(raw))
|
|
399
|
+
name, args, ok := generic(raw)
|
|
400
|
+
if !ok || name != "ReceiveType" && !strings.HasSuffix(name, ".ReceiveType") || len(args) == 0 {
|
|
401
|
+
return "", false
|
|
402
|
+
}
|
|
403
|
+
return firstArg(args), true
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
func receiveTypeForCall(info *fileInfo, reg *registry, fn functionInfo, call sourceCallInfo) (string, int, bool) {
|
|
407
|
+
paramIndex, typeText, ok := receiveTypeParameter(fn)
|
|
408
|
+
if !ok || len(call.args) >= paramIndex+1 {
|
|
409
|
+
return "", 0, false
|
|
410
|
+
}
|
|
411
|
+
substitutions := map[string]string{}
|
|
412
|
+
for i, name := range fn.typeParams {
|
|
413
|
+
if i < len(call.typeArgs) {
|
|
414
|
+
substitutions[name] = call.typeArgs[i]
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for i := 0; i < paramIndex && i < len(call.args); i++ {
|
|
418
|
+
for _, typeParam := range fn.typeParams {
|
|
419
|
+
if substitutions[typeParam] != "" || !typeTextContainsTypeParameter(fn.params[i].typeText, typeParam) {
|
|
420
|
+
continue
|
|
421
|
+
}
|
|
422
|
+
if inferred, ok := inferTypeParameterFromArgument(fn.params[i].typeText, typeParam, call.args[i]); ok {
|
|
423
|
+
substitutions[typeParam] = inferred
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
for _, typeParam := range fn.typeParams {
|
|
428
|
+
if replacement := substitutions[typeParam]; replacement != "" {
|
|
429
|
+
typeText = replaceTypeParameter(typeText, typeParam, replacement)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
typeText = unwrapReceiveTypeHelperType(typeText)
|
|
433
|
+
if hasUnresolvedTypeParameters(typeText, fn.typeParams) {
|
|
434
|
+
availableArgs := min(len(call.args), paramIndex)
|
|
435
|
+
if inferred, ok := uniqueTypedFunctionArgumentParameter(info, call.args[:availableArgs], call.pos); ok {
|
|
436
|
+
return inferred, paramIndex, true
|
|
437
|
+
}
|
|
438
|
+
return "", 0, false
|
|
439
|
+
}
|
|
440
|
+
if !receiveTypeMetadataResolvable(info, reg, typeText) {
|
|
441
|
+
availableArgs := min(len(call.args), paramIndex)
|
|
442
|
+
if inferred, ok := uniqueTypedFunctionArgumentParameter(info, call.args[:availableArgs], call.pos); ok {
|
|
443
|
+
return inferred, paramIndex, true
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return typeText, paramIndex, true
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
func receiveTypeMethodForCall(info *fileInfo, reg *registry, fns []functionInfo, call sourceCallInfo) (string, int, bool) {
|
|
450
|
+
if owner, ok := receiverClassName(info, reg, call); ok {
|
|
451
|
+
fns = methodCandidatesForOwner(fns, owner)
|
|
452
|
+
if len(fns) == 0 {
|
|
453
|
+
return "", 0, false
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
type match struct {
|
|
457
|
+
typeText string
|
|
458
|
+
metadataArgIndex int
|
|
459
|
+
}
|
|
460
|
+
matches := []match{}
|
|
461
|
+
for _, fn := range fns {
|
|
462
|
+
typeText, metadataArgIndex, ok := receiveTypeForCall(info, reg, fn, call)
|
|
463
|
+
if ok {
|
|
464
|
+
matches = append(matches, match{typeText: typeText, metadataArgIndex: metadataArgIndex})
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if len(matches) == 1 {
|
|
468
|
+
return matches[0].typeText, matches[0].metadataArgIndex, true
|
|
469
|
+
}
|
|
470
|
+
if len(matches) == 0 {
|
|
471
|
+
return "", 0, false
|
|
472
|
+
}
|
|
473
|
+
first := matches[0]
|
|
474
|
+
for _, next := range matches[1:] {
|
|
475
|
+
if next.typeText != first.typeText || next.metadataArgIndex != first.metadataArgIndex {
|
|
476
|
+
return "", 0, false
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return first.typeText, first.metadataArgIndex, true
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
func methodCandidatesForOwner(fns []functionInfo, owner string) []functionInfo {
|
|
483
|
+
out := []functionInfo{}
|
|
484
|
+
for _, fn := range fns {
|
|
485
|
+
if fn.owner == owner {
|
|
486
|
+
out = append(out, fn)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return out
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
func typeTextContainsTypeParameter(raw string, typeParam string) bool {
|
|
493
|
+
return regexp.MustCompile(`\b` + regexp.QuoteMeta(typeParam) + `\b`).MatchString(raw)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
func inferTypeParameterFromArgument(paramType string, typeParam string, arg string) (string, bool) {
|
|
497
|
+
if strings.TrimSpace(trimParens(paramType)) == typeParam {
|
|
498
|
+
return argumentTypeText(arg)
|
|
499
|
+
}
|
|
500
|
+
if !typeTextContainsTypeParameter(paramType, typeParam) {
|
|
501
|
+
return "", false
|
|
502
|
+
}
|
|
503
|
+
return firstFunctionParameterType(arg)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
func argumentTypeText(raw string) (string, bool) {
|
|
507
|
+
raw = strings.TrimSpace(raw)
|
|
508
|
+
if raw == "true" || raw == "false" || raw == "null" || raw == "undefined" {
|
|
509
|
+
return raw, true
|
|
510
|
+
}
|
|
511
|
+
if strings.HasPrefix(raw, "'") || strings.HasPrefix(raw, "\"") || strings.HasPrefix(raw, "`") {
|
|
512
|
+
return raw, true
|
|
513
|
+
}
|
|
514
|
+
if _, err := strconv.ParseFloat(raw, 64); err == nil {
|
|
515
|
+
return raw, true
|
|
516
|
+
}
|
|
517
|
+
return firstFunctionParameterType(raw)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
func unwrapReceiveTypeHelperType(raw string) string {
|
|
521
|
+
for {
|
|
522
|
+
raw = strings.TrimSpace(trimParens(raw))
|
|
523
|
+
name, args, ok := generic(raw)
|
|
524
|
+
if !ok || len(args) == 0 {
|
|
525
|
+
return raw
|
|
526
|
+
}
|
|
527
|
+
if name != "NoInfer" {
|
|
528
|
+
return raw
|
|
529
|
+
}
|
|
530
|
+
raw = firstArg(args)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
func receiveTypeMetadataResolvable(info *fileInfo, reg *registry, raw string) bool {
|
|
535
|
+
raw = unwrapReceiveTypeHelperType(raw)
|
|
536
|
+
name, args, ok := generic(raw)
|
|
537
|
+
if !ok {
|
|
538
|
+
return !isUnsupportedTypeSyntax(raw)
|
|
539
|
+
}
|
|
540
|
+
if alias, owner, _, ok := resolveAliasRef(info, reg, name); ok && len(alias.params) > 0 {
|
|
541
|
+
body := alias.body
|
|
542
|
+
for i := range alias.params {
|
|
543
|
+
body = replaceTypeParameter(body, aliasParamName(alias, i), aliasArg(alias, args, i))
|
|
544
|
+
}
|
|
545
|
+
if shouldUseTypiaTypeCtx(owner, reg, body, map[string]bool{}) {
|
|
546
|
+
return true
|
|
547
|
+
}
|
|
548
|
+
return !isUnsupportedTypeSyntax(body)
|
|
549
|
+
}
|
|
550
|
+
switch name {
|
|
551
|
+
case "Array", "ReadonlyArray", "Promise", "NoInfer", "NonNullable", "ApiResponse",
|
|
552
|
+
"HttpBody", "HttpQueries", "HttpQuery", "HttpPath", "HttpHeader",
|
|
553
|
+
"ApiName", "ApiType", "MinLength", "MaxLength", "Minimum", "GreaterThan", "Maximum", "LessThan", "Pattern",
|
|
554
|
+
"Validate", "DatabaseField", "MySQL", "Reference", "Index", "Indexed", "Unique", "PrimaryKey",
|
|
555
|
+
"AutoIncrement", "TypeAnnotation", "Record", "EntityFields", "EntityOptionals",
|
|
556
|
+
"NewEntityFields", "Pick", "Omit", "Partial", "Required", "Extract":
|
|
557
|
+
return true
|
|
558
|
+
default:
|
|
559
|
+
if ref, ok := info.imports[name]; ok && isExternalImportRef(ref) {
|
|
560
|
+
return true
|
|
561
|
+
}
|
|
562
|
+
return false
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
func uniqueTypedFunctionArgumentParameter(info *fileInfo, args []string, pos int) (string, bool) {
|
|
567
|
+
found := ""
|
|
568
|
+
for _, arg := range args {
|
|
569
|
+
typeText, ok := functionArgumentParameterType(info, arg, pos)
|
|
570
|
+
if !ok {
|
|
571
|
+
continue
|
|
572
|
+
}
|
|
573
|
+
if found != "" {
|
|
574
|
+
return "", false
|
|
575
|
+
}
|
|
576
|
+
found = typeText
|
|
577
|
+
}
|
|
578
|
+
return found, found != ""
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
func functionArgumentParameterType(info *fileInfo, raw string, pos int) (string, bool) {
|
|
582
|
+
if typeText, ok := firstFunctionParameterType(raw); ok {
|
|
583
|
+
return typeText, true
|
|
584
|
+
}
|
|
585
|
+
raw = strings.TrimSpace(raw)
|
|
586
|
+
if !isIdentifierName(raw) {
|
|
587
|
+
return "", false
|
|
588
|
+
}
|
|
589
|
+
for _, fn := range localFunctionsBefore(info.functions[raw], pos) {
|
|
590
|
+
if len(fn.params) > 0 {
|
|
591
|
+
return fn.params[0].typeText, fn.params[0].typeText != ""
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return localFunctionVariableParameterType(info.file.Text(), raw, pos)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
func localFunctionsBefore(fns []functionInfo, pos int) []functionInfo {
|
|
598
|
+
out := []functionInfo{}
|
|
599
|
+
for _, fn := range fns {
|
|
600
|
+
if fn.pos <= pos {
|
|
601
|
+
out = append(out, fn)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
sort.SliceStable(out, func(i, j int) bool { return out[i].pos > out[j].pos })
|
|
605
|
+
return out
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
func localFunctionVariableParameterType(text string, name string, pos int) (string, bool) {
|
|
609
|
+
if pos > len(text) {
|
|
610
|
+
pos = len(text)
|
|
611
|
+
}
|
|
612
|
+
prefix := text[:pos]
|
|
613
|
+
ident := regexp.QuoteMeta(name)
|
|
614
|
+
for _, pattern := range []string{
|
|
615
|
+
`(?:^|[^A-Za-z0-9_$])(?:const|let|var)\s+` + ident + `\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>`,
|
|
616
|
+
`(?:^|[^A-Za-z0-9_$])(?:const|let|var)\s+` + ident + `\s*:\s*\(([^)]*)\)\s*=>`,
|
|
617
|
+
} {
|
|
618
|
+
if params, ok := lastCapture(prefix, pattern); ok {
|
|
619
|
+
return firstParameterType(params)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return "", false
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
func sourceCalls(text string, name string) []sourceCallInfo {
|
|
626
|
+
out := []sourceCallInfo{}
|
|
627
|
+
search := 0
|
|
628
|
+
for search < len(text) {
|
|
629
|
+
idx := strings.Index(text[search:], name)
|
|
630
|
+
if idx < 0 {
|
|
631
|
+
break
|
|
632
|
+
}
|
|
633
|
+
start := search + idx
|
|
634
|
+
after := start + len(name)
|
|
635
|
+
if start > 0 && isIdent(text[start-1]) {
|
|
636
|
+
search = after
|
|
637
|
+
continue
|
|
638
|
+
}
|
|
639
|
+
if after < len(text) && isIdent(text[after]) {
|
|
640
|
+
search = after
|
|
641
|
+
continue
|
|
642
|
+
}
|
|
643
|
+
if !isCodePosition(text, start) || isFunctionDeclarationName(text, start) {
|
|
644
|
+
search = after
|
|
645
|
+
continue
|
|
646
|
+
}
|
|
647
|
+
typeArgs := []string{}
|
|
648
|
+
pos := skipSpace(text, after)
|
|
649
|
+
if pos < len(text) && text[pos] == '<' {
|
|
650
|
+
typeEnd := findBalanced(text, pos, '<', '>')
|
|
651
|
+
if typeEnd < 0 {
|
|
652
|
+
search = pos + 1
|
|
653
|
+
continue
|
|
654
|
+
}
|
|
655
|
+
typeArgs = nonEmptyParts(splitTop(text[pos+1:typeEnd], ","))
|
|
656
|
+
pos = skipSpace(text, typeEnd+1)
|
|
657
|
+
}
|
|
658
|
+
if pos >= len(text) || text[pos] != '(' {
|
|
659
|
+
search = after
|
|
660
|
+
continue
|
|
661
|
+
}
|
|
662
|
+
close := findBalanced(text, pos, '(', ')')
|
|
663
|
+
if close < 0 {
|
|
664
|
+
search = pos + 1
|
|
665
|
+
continue
|
|
666
|
+
}
|
|
667
|
+
if isDeclarationCallShape(text, close) {
|
|
668
|
+
search = close + 1
|
|
669
|
+
continue
|
|
670
|
+
}
|
|
671
|
+
out = append(out, sourceCallInfo{name: name, typeArgs: typeArgs, args: nonEmptyParts(splitTop(text[pos+1:close], ",")), pos: start})
|
|
672
|
+
search = close + 1
|
|
673
|
+
}
|
|
674
|
+
return out
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
func sourceMethodCalls(text string, name string) []sourceCallInfo {
|
|
678
|
+
out := []sourceCallInfo{}
|
|
679
|
+
for _, call := range sourceCalls(text, name) {
|
|
680
|
+
before := call.pos - 1
|
|
681
|
+
for before >= 0 && (text[before] == ' ' || text[before] == '\t' || text[before] == '\r' || text[before] == '\n') {
|
|
682
|
+
before--
|
|
683
|
+
}
|
|
684
|
+
if before >= 0 && text[before] == '.' {
|
|
685
|
+
call.receiver = methodReceiverExpression(text, before)
|
|
686
|
+
out = append(out, call)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return out
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
func methodReceiverExpression(text string, dot int) string {
|
|
693
|
+
end := dot
|
|
694
|
+
pos := dot - 1
|
|
695
|
+
for pos >= 0 && (text[pos] == ' ' || text[pos] == '\t' || text[pos] == '\r' || text[pos] == '\n') {
|
|
696
|
+
pos--
|
|
697
|
+
}
|
|
698
|
+
if pos >= 0 && text[pos] == '?' {
|
|
699
|
+
pos--
|
|
700
|
+
for pos >= 0 && (text[pos] == ' ' || text[pos] == '\t' || text[pos] == '\r' || text[pos] == '\n') {
|
|
701
|
+
pos--
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
start, ok := scanIdentifierLeft(text, pos)
|
|
705
|
+
if !ok {
|
|
706
|
+
return ""
|
|
707
|
+
}
|
|
708
|
+
for {
|
|
709
|
+
prev := start - 1
|
|
710
|
+
for prev >= 0 && (text[prev] == ' ' || text[prev] == '\t' || text[prev] == '\r' || text[prev] == '\n') {
|
|
711
|
+
prev--
|
|
712
|
+
}
|
|
713
|
+
if prev < 0 || text[prev] != '.' {
|
|
714
|
+
break
|
|
715
|
+
}
|
|
716
|
+
leftEnd := prev - 1
|
|
717
|
+
for leftEnd >= 0 && (text[leftEnd] == ' ' || text[leftEnd] == '\t' || text[leftEnd] == '\r' || text[leftEnd] == '\n') {
|
|
718
|
+
leftEnd--
|
|
719
|
+
}
|
|
720
|
+
if leftEnd >= 0 && text[leftEnd] == '?' {
|
|
721
|
+
leftEnd--
|
|
722
|
+
for leftEnd >= 0 && (text[leftEnd] == ' ' || text[leftEnd] == '\t' || text[leftEnd] == '\r' || text[leftEnd] == '\n') {
|
|
723
|
+
leftEnd--
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
leftStart, ok := scanIdentifierLeft(text, leftEnd)
|
|
727
|
+
if !ok {
|
|
728
|
+
return ""
|
|
729
|
+
}
|
|
730
|
+
start = leftStart
|
|
731
|
+
}
|
|
732
|
+
return strings.TrimSpace(text[start:end])
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
func scanIdentifierLeft(text string, pos int) (int, bool) {
|
|
736
|
+
if pos < 0 || pos >= len(text) || !isIdent(text[pos]) {
|
|
737
|
+
return 0, false
|
|
738
|
+
}
|
|
739
|
+
end := pos
|
|
740
|
+
for pos >= 0 && isIdent(text[pos]) {
|
|
741
|
+
pos--
|
|
742
|
+
}
|
|
743
|
+
start := pos + 1
|
|
744
|
+
if start <= end && text[start] >= '0' && text[start] <= '9' {
|
|
745
|
+
return 0, false
|
|
746
|
+
}
|
|
747
|
+
return start, true
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
func receiverClassName(info *fileInfo, reg *registry, call sourceCallInfo) (string, bool) {
|
|
751
|
+
receiver := strings.TrimSpace(call.receiver)
|
|
752
|
+
if receiver == "" {
|
|
753
|
+
return "", false
|
|
754
|
+
}
|
|
755
|
+
if receiver == "this" {
|
|
756
|
+
if class := containingClass(info, call.pos); class != nil {
|
|
757
|
+
return class.name, true
|
|
758
|
+
}
|
|
759
|
+
return "", false
|
|
760
|
+
}
|
|
761
|
+
if strings.HasPrefix(receiver, "this.") {
|
|
762
|
+
class := containingClass(info, call.pos)
|
|
763
|
+
if class == nil {
|
|
764
|
+
return "", false
|
|
765
|
+
}
|
|
766
|
+
member := strings.TrimSpace(strings.TrimPrefix(receiver, "this."))
|
|
767
|
+
if strings.Contains(member, ".") {
|
|
768
|
+
return "", false
|
|
769
|
+
}
|
|
770
|
+
if typeText, ok := classMemberType(class, member); ok {
|
|
771
|
+
return classNameFromType(info, reg, typeText, call.pos)
|
|
772
|
+
}
|
|
773
|
+
return "", false
|
|
774
|
+
}
|
|
775
|
+
if !isIdentifierName(receiver) {
|
|
776
|
+
return "", false
|
|
777
|
+
}
|
|
778
|
+
if typeText, ok := localReceiverType(info.file.Text(), receiver, call.pos); ok {
|
|
779
|
+
return classNameFromType(info, reg, typeText, call.pos)
|
|
780
|
+
}
|
|
781
|
+
return "", false
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
func containingClass(info *fileInfo, pos int) *classInfo {
|
|
785
|
+
var found *classInfo
|
|
786
|
+
for _, class := range info.classes {
|
|
787
|
+
if class.pos <= pos && pos <= class.end && (found == nil || class.pos > found.pos) {
|
|
788
|
+
found = class
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return found
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
func classMemberType(class *classInfo, name string) (string, bool) {
|
|
795
|
+
for _, prop := range class.properties {
|
|
796
|
+
if prop.name == name {
|
|
797
|
+
return prop.typeText, true
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
for _, param := range class.ctor {
|
|
801
|
+
if param.name == name {
|
|
802
|
+
return param.typeText, true
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return "", false
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
func localReceiverType(text string, name string, pos int) (string, bool) {
|
|
809
|
+
if pos > len(text) {
|
|
810
|
+
pos = len(text)
|
|
811
|
+
}
|
|
812
|
+
prefix := text[:pos]
|
|
813
|
+
ident := regexp.QuoteMeta(name)
|
|
814
|
+
if typeText, ok := lastCapture(prefix, `(?:^|[^A-Za-z0-9_$])(?:const|let|var)\s+`+ident+`\s*=\s*new\s+([A-Za-z_$][A-Za-z0-9_$]*)\b`); ok {
|
|
815
|
+
return typeText, true
|
|
816
|
+
}
|
|
817
|
+
if typeText, ok := lastCapture(prefix, `(?:^|[^A-Za-z0-9_$])(?:const|let|var)\s+`+ident+`\s*:\s*([^=;\n]+)`); ok {
|
|
818
|
+
return typeText, true
|
|
819
|
+
}
|
|
820
|
+
if typeText, ok := lastCapture(prefix, `(?:^|[^A-Za-z0-9_$])`+ident+`\s*:\s*([A-Za-z_$][A-Za-z0-9_$]*(?:\s*<[^=;,\)\n]+>)?)`); ok {
|
|
821
|
+
return typeText, true
|
|
822
|
+
}
|
|
823
|
+
return "", false
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
func lastCapture(text string, pattern string) (string, bool) {
|
|
827
|
+
re := regexp.MustCompile(pattern)
|
|
828
|
+
matches := re.FindAllStringSubmatch(text, -1)
|
|
829
|
+
if len(matches) == 0 || len(matches[len(matches)-1]) < 2 {
|
|
830
|
+
return "", false
|
|
831
|
+
}
|
|
832
|
+
value := strings.TrimSpace(matches[len(matches)-1][1])
|
|
833
|
+
return value, value != ""
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
func classNameFromType(info *fileInfo, reg *registry, typeText string, pos int) (string, bool) {
|
|
837
|
+
name := interfaceRefName(typeText)
|
|
838
|
+
if class, _, ok := resolveClassRefAt(info, reg, name, pos); ok {
|
|
839
|
+
return class.name, true
|
|
840
|
+
}
|
|
841
|
+
if _, ok := reg.classes[name]; ok {
|
|
842
|
+
return name, true
|
|
843
|
+
}
|
|
844
|
+
return "", false
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
func isDeclarationCallShape(text string, close int) bool {
|
|
848
|
+
next := skipSpace(text, close+1)
|
|
849
|
+
if next >= len(text) {
|
|
850
|
+
return false
|
|
851
|
+
}
|
|
852
|
+
return text[next] == ':' || text[next] == '{'
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
func isFunctionDeclarationName(text string, start int) bool {
|
|
856
|
+
lineStart := strings.LastIndexAny(text[:start], "\r\n")
|
|
857
|
+
if lineStart < 0 {
|
|
858
|
+
lineStart = 0
|
|
859
|
+
} else {
|
|
860
|
+
lineStart++
|
|
861
|
+
}
|
|
862
|
+
prefix := strings.TrimSpace(text[lineStart:start])
|
|
863
|
+
return regexp.MustCompile(`(?:^|\b)(?:export\s+)?(?:async\s+)?function\s*$`).MatchString(prefix)
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
func firstFunctionParameterType(raw string) (string, bool) {
|
|
867
|
+
raw = strings.TrimSpace(raw)
|
|
868
|
+
raw = strings.TrimPrefix(raw, "async ")
|
|
869
|
+
if strings.HasPrefix(raw, "function") {
|
|
870
|
+
open := strings.IndexByte(raw, '(')
|
|
871
|
+
if open < 0 {
|
|
872
|
+
return "", false
|
|
873
|
+
}
|
|
874
|
+
close := findBalanced(raw, open, '(', ')')
|
|
875
|
+
if close < 0 {
|
|
876
|
+
return "", false
|
|
877
|
+
}
|
|
878
|
+
return firstParameterType(raw[open+1 : close])
|
|
879
|
+
}
|
|
880
|
+
if strings.HasPrefix(raw, "(") {
|
|
881
|
+
close := findBalanced(raw, 0, '(', ')')
|
|
882
|
+
if close < 0 {
|
|
883
|
+
return "", false
|
|
884
|
+
}
|
|
885
|
+
after := skipSpace(raw, close+1)
|
|
886
|
+
if after >= len(raw) || !strings.HasPrefix(raw[after:], "=>") {
|
|
887
|
+
return "", false
|
|
888
|
+
}
|
|
889
|
+
return firstParameterType(raw[1:close])
|
|
890
|
+
}
|
|
891
|
+
arrow := strings.Index(raw, "=>")
|
|
892
|
+
if arrow < 0 {
|
|
893
|
+
return "", false
|
|
894
|
+
}
|
|
895
|
+
return firstParameterType(raw[:arrow])
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
func firstParameterType(params string) (string, bool) {
|
|
899
|
+
first := firstArg(splitTop(params, ","))
|
|
900
|
+
if strings.TrimSpace(first) == "" {
|
|
901
|
+
return "", false
|
|
902
|
+
}
|
|
903
|
+
colon := topLevelColon(first)
|
|
904
|
+
if colon < 0 {
|
|
905
|
+
return "", false
|
|
906
|
+
}
|
|
907
|
+
typeText := strings.TrimSpace(first[colon+1:])
|
|
908
|
+
if eq := topLevelEquals(typeText); eq >= 0 {
|
|
909
|
+
typeText = strings.TrimSpace(typeText[:eq])
|
|
910
|
+
}
|
|
911
|
+
return typeText, typeText != ""
|
|
912
|
+
}
|