typenative 0.0.20 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish-github-packages.yml +14 -6
- package/CHANGELOG.md +70 -0
- package/README.md +41 -4
- package/TODO.md +38 -0
- package/bin/cli.js +62 -0
- package/bin/index.js +105 -134
- package/bin/main.js +31 -0
- package/bin/parse-native.js +37 -0
- package/bin/parse-node.js +110 -0
- package/bin/prompt.js +29 -0
- package/bin/transpiler.js +4277 -629
- package/package.json +56 -37
- package/tsparser/go.mod +14 -0
- package/tsparser/go.sum +22 -0
- package/tsparser/main.go +279 -0
- package/tsparser/tsparser.exe +4 -0
- package/types/typenative-npm.d.ts +44 -0
package/package.json
CHANGED
|
@@ -1,49 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typenative",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Build native applications using Typescript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"typenative": "bin/
|
|
7
|
+
"typenative": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"types": "./index.d.ts",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"watch": "tsc --watch",
|
|
13
|
+
"build:native": "node scripts/build-native.mjs",
|
|
13
14
|
"test": "node ./test/test-runner test",
|
|
15
|
+
"features": "node scripts/features.mjs",
|
|
14
16
|
"release": "npm publish",
|
|
15
|
-
"test1": "node ./bin/
|
|
16
|
-
"test2": "node ./bin/
|
|
17
|
-
"test3": "node ./bin/
|
|
17
|
+
"test1": "node ./bin/cli.js --source test/test1.spec.ts --script",
|
|
18
|
+
"test2": "node ./bin/cli.js --source test/test2.spec.ts --script",
|
|
19
|
+
"test3": "node ./bin/cli.js --source test/test3.spec.ts --script",
|
|
18
20
|
"test3_node": "tsc test/test3.spec.ts && node test/test3.spec.js && rimraf test/test3.spec.js",
|
|
19
|
-
"test4": "node ./bin/
|
|
20
|
-
"test5": "node ./bin/
|
|
21
|
-
"test6": "node ./bin/
|
|
22
|
-
"test7": "node ./bin/
|
|
23
|
-
"test8": "node ./bin/
|
|
24
|
-
"test9": "node ./bin/
|
|
25
|
-
"test10": "node ./bin/
|
|
26
|
-
"test11": "node ./bin/
|
|
27
|
-
"test12": "node ./bin/
|
|
28
|
-
"test13": "node ./bin/
|
|
29
|
-
"test14": "node ./bin/
|
|
30
|
-
"test15": "node ./bin/
|
|
31
|
-
"test16": "node ./bin/
|
|
32
|
-
"test17": "node ./bin/
|
|
33
|
-
"test18": "node ./bin/
|
|
34
|
-
"test19": "node ./bin/
|
|
35
|
-
"test20": "node ./bin/
|
|
36
|
-
"test21": "node ./bin/
|
|
37
|
-
"test22": "node ./bin/
|
|
38
|
-
"test23": "node ./bin/
|
|
39
|
-
"test24": "node ./bin/
|
|
40
|
-
"test25": "node ./bin/
|
|
41
|
-
"test25_export": "node ./bin/
|
|
42
|
-
"test26": "node ./bin/
|
|
43
|
-
"test27": "node ./bin/
|
|
44
|
-
"test28": "node ./bin/
|
|
45
|
-
"test29": "node ./bin/
|
|
46
|
-
"test30": "node ./bin/
|
|
21
|
+
"test4": "node ./bin/cli.js --source test/test4.spec.ts --script",
|
|
22
|
+
"test5": "node ./bin/cli.js --source test/test5.spec.ts --script",
|
|
23
|
+
"test6": "node ./bin/cli.js --source test/test6.spec.ts --script",
|
|
24
|
+
"test7": "node ./bin/cli.js --source test/test7.spec.ts --script",
|
|
25
|
+
"test8": "node ./bin/cli.js --source test/test8.spec.ts --script",
|
|
26
|
+
"test9": "node ./bin/cli.js --source test/test9.spec.ts --script",
|
|
27
|
+
"test10": "node ./bin/cli.js --source test/test10.spec.ts --script",
|
|
28
|
+
"test11": "node ./bin/cli.js --source test/test11.spec.ts --script",
|
|
29
|
+
"test12": "node ./bin/cli.js --source test/test12.spec.ts --script",
|
|
30
|
+
"test13": "node ./bin/cli.js --source test/test13.spec.ts --script",
|
|
31
|
+
"test14": "node ./bin/cli.js --source test/test14.spec.ts --script",
|
|
32
|
+
"test15": "node ./bin/cli.js --source test/test15.spec.ts --script",
|
|
33
|
+
"test16": "node ./bin/cli.js --source test/test16.spec.ts --script",
|
|
34
|
+
"test17": "node ./bin/cli.js --source test/test17.spec.ts --script",
|
|
35
|
+
"test18": "node ./bin/cli.js --source test/test18.spec.ts --script",
|
|
36
|
+
"test19": "node ./bin/cli.js --source test/test19.spec.ts --script",
|
|
37
|
+
"test20": "node ./bin/cli.js --source test/test20.spec.ts --script",
|
|
38
|
+
"test21": "node ./bin/cli.js --source test/test21.spec.ts --script",
|
|
39
|
+
"test22": "node ./bin/cli.js --source test/test22.spec.ts --script",
|
|
40
|
+
"test23": "node ./bin/cli.js --source test/test23.spec.ts --script",
|
|
41
|
+
"test24": "node ./bin/cli.js --source test/test24.spec.ts --script",
|
|
42
|
+
"test25": "node ./bin/cli.js --source test/test25.spec.ts --script",
|
|
43
|
+
"test25_export": "node ./bin/cli.js --source test/test25-export.spec.ts --script",
|
|
44
|
+
"test26": "node ./bin/cli.js --source test/test26.spec.ts --script",
|
|
45
|
+
"test27": "node ./bin/cli.js --source test/test27.spec.ts --script",
|
|
46
|
+
"test28": "node ./bin/cli.js --source test/test28.spec.ts --script",
|
|
47
|
+
"test29": "node ./bin/cli.js --source test/test29.spec.ts --script",
|
|
48
|
+
"test30": "node ./bin/cli.js --source test/test30.spec.ts --script",
|
|
49
|
+
"test31": "node ./bin/cli.js --source test/test31.spec.ts --script",
|
|
50
|
+
"test32": "node ./bin/cli.js --source test/test32.spec.ts --script",
|
|
51
|
+
"test33": "node ./bin/cli.js --source test/test33.spec.ts --script",
|
|
52
|
+
"test34": "node ./bin/cli.js --source test/test34.spec.ts --script",
|
|
53
|
+
"test35": "node ./bin/cli.js --source test/test35.spec.ts --script",
|
|
54
|
+
"test36": "node ./bin/cli.js --source test/test36.spec.ts --script",
|
|
55
|
+
"test37": "node ./bin/cli.js --source test/test37.spec.ts --script",
|
|
56
|
+
"test38": "node ./bin/cli.js --source test/test38.spec.ts --script",
|
|
57
|
+
"test39": "node ./bin/cli.js --source test/test39.spec.ts --script",
|
|
58
|
+
"test40": "node ./bin/cli.js --source test/test40.spec.ts --script",
|
|
59
|
+
"test41": "node ./bin/cli.js --source test/test41.spec.ts --script",
|
|
60
|
+
"test42": "node ./bin/cli.js --source test/test42.spec.ts --script",
|
|
61
|
+
"test43": "node ./bin/cli.js --source test/test43.spec.ts --script",
|
|
62
|
+
"test44": "node ./bin/cli.js --source test/test44.spec.ts --script",
|
|
63
|
+
"test45": "node ./bin/cli.js --source test/test45.spec.ts --script",
|
|
64
|
+
"test46": "node ./bin/cli.js --source test/test46.spec.ts --script",
|
|
65
|
+
"test47": "node ./bin/cli.js --source test/test47.spec.ts --script",
|
|
66
|
+
"test48": "node ./bin/cli.js --source test/test48.spec.ts --script",
|
|
67
|
+
"test49": "node ./bin/cli.js --source test/test49.spec.ts --script",
|
|
68
|
+
"test50": "node ./bin/cli.js --source test/test50.spec.ts --script"
|
|
47
69
|
},
|
|
48
70
|
"repository": {
|
|
49
71
|
"type": "git",
|
|
@@ -56,16 +78,13 @@
|
|
|
56
78
|
},
|
|
57
79
|
"homepage": "https://github.com/danisss9/typenative#readme",
|
|
58
80
|
"devDependencies": {
|
|
59
|
-
"@types/fs-extra": "^11.0.4",
|
|
60
81
|
"@types/inquirer": "^9.0.9",
|
|
61
82
|
"@types/node": "^25.3.5",
|
|
83
|
+
"execa": "^9.6.1",
|
|
62
84
|
"rimraf": "^6.1.3"
|
|
63
85
|
},
|
|
64
86
|
"dependencies": {
|
|
65
|
-
"execa": "^9.6.1",
|
|
66
|
-
"fs-extra": "^11.3.4",
|
|
67
87
|
"inquirer": "^13.3.0",
|
|
68
|
-
"nanoid": "^5.1.6",
|
|
69
88
|
"typescript": "^5.9.3"
|
|
70
89
|
}
|
|
71
90
|
}
|
package/tsparser/go.mod
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module github.com/danisss9/typenative/tsparser
|
|
2
|
+
|
|
3
|
+
go 1.26.0
|
|
4
|
+
|
|
5
|
+
require github.com/buke/typescript-go-internal v0.0.2
|
|
6
|
+
|
|
7
|
+
require (
|
|
8
|
+
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e // indirect
|
|
9
|
+
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
|
10
|
+
github.com/zeebo/xxh3 v1.1.0 // indirect
|
|
11
|
+
golang.org/x/sync v0.20.0 // indirect
|
|
12
|
+
golang.org/x/sys v0.43.0 // indirect
|
|
13
|
+
golang.org/x/text v0.36.0 // indirect
|
|
14
|
+
)
|
package/tsparser/go.sum
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
github.com/buke/typescript-go-internal v0.0.2 h1:JTHAXQfj1HVN4eXh7XqUWZurxjowXoE+5Ud8QLS4uRg=
|
|
2
|
+
github.com/buke/typescript-go-internal v0.0.2/go.mod h1:nZjFbdZyxx5D9s/GafAh6ESVUy3GMAjNMMMBjVEqDgc=
|
|
3
|
+
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
|
4
|
+
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
|
5
|
+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
6
|
+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
7
|
+
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
|
8
|
+
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
|
9
|
+
github.com/peter-evans/patience v0.3.0 h1:rX0JdJeepqdQl1Sk9c9uvorjYYzL2TfgLX1adqYm9cA=
|
|
10
|
+
github.com/peter-evans/patience v0.3.0/go.mod h1:Kmxu5sY1NmBLFSStvXjX1wS9mIv7wMcP/ubucyMOAu0=
|
|
11
|
+
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
|
12
|
+
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
|
13
|
+
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
|
14
|
+
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
|
15
|
+
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
|
16
|
+
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
|
17
|
+
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
|
18
|
+
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
19
|
+
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
|
20
|
+
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
|
21
|
+
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
|
22
|
+
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
package/tsparser/main.go
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// tsparser: parses TypeScript source into a normalized JSON AST.
|
|
2
|
+
//
|
|
3
|
+
// Used by TypeNative's self-hosting pipeline: the transpiled compiler calls
|
|
4
|
+
// this tool (via node:child_process) instead of the `typescript` npm package.
|
|
5
|
+
// The JSON shape mirrors the TypeScript API AST (kind + spec field names) so
|
|
6
|
+
// the same transpiler code can consume ASTs from either source.
|
|
7
|
+
//
|
|
8
|
+
// Usage: tsparser < file.ts > ast.json
|
|
9
|
+
package main
|
|
10
|
+
|
|
11
|
+
import (
|
|
12
|
+
"encoding/json"
|
|
13
|
+
"fmt"
|
|
14
|
+
"io"
|
|
15
|
+
"os"
|
|
16
|
+
"reflect"
|
|
17
|
+
"strings"
|
|
18
|
+
|
|
19
|
+
"github.com/buke/typescript-go-internal/pkg/ast"
|
|
20
|
+
"github.com/buke/typescript-go-internal/pkg/core"
|
|
21
|
+
"github.com/buke/typescript-go-internal/pkg/parser"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
func main() {
|
|
25
|
+
sourceBytes, err := io.ReadAll(os.Stdin)
|
|
26
|
+
if err != nil {
|
|
27
|
+
fmt.Fprintln(os.Stderr, "read stdin:", err)
|
|
28
|
+
os.Exit(1)
|
|
29
|
+
}
|
|
30
|
+
sourceFile := parser.ParseSourceFile(
|
|
31
|
+
ast.SourceFileParseOptions{FileName: "/main.ts"},
|
|
32
|
+
string(sourceBytes),
|
|
33
|
+
core.ScriptKindTS,
|
|
34
|
+
)
|
|
35
|
+
out := serializeNode(sourceFile.AsNode())
|
|
36
|
+
enc := json.NewEncoder(os.Stdout)
|
|
37
|
+
enc.SetIndent("", " ")
|
|
38
|
+
if err := enc.Encode(out); err != nil {
|
|
39
|
+
fmt.Fprintln(os.Stderr, "encode:", err)
|
|
40
|
+
os.Exit(1)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// Nodes currently being serialized (cycle guard).
|
|
46
|
+
var inProgress = map[*ast.Node]bool{}
|
|
47
|
+
|
|
48
|
+
// Nodes whose As<Kind> method name doesn't follow the naming patterns.
|
|
49
|
+
var asMethodOverrides = map[string]string{
|
|
50
|
+
"ForInStatement": "AsForInOrOfStatement",
|
|
51
|
+
"ForOfStatement": "AsForInOrOfStatement",
|
|
52
|
+
"ArrayBindingPattern": "AsBindingPattern",
|
|
53
|
+
"ObjectBindingPattern": "AsBindingPattern",
|
|
54
|
+
"CaseClause": "AsCaseOrDefaultClause",
|
|
55
|
+
"DefaultClause": "AsCaseOrDefaultClause",
|
|
56
|
+
"TypeAssertionExpression": "AsTypeAssertion",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// asData returns the typed data struct behind a node via its As<Kind> method.
|
|
60
|
+
// Method names don't always match kind names exactly (GetAccessor →
|
|
61
|
+
// AsGetAccessorDeclaration), so common suffixes are tried in order.
|
|
62
|
+
func asData(n *ast.Node) reflect.Value {
|
|
63
|
+
kind := n.Kind.String()
|
|
64
|
+
if strings.HasPrefix(kind, "Kind") {
|
|
65
|
+
kind = kind[4:] // KindIfStatement → IfStatement
|
|
66
|
+
}
|
|
67
|
+
nodeVal := reflect.ValueOf(n)
|
|
68
|
+
candidates := []string{"As" + kind, "As" + kind + "Declaration", "As" + kind + "Expression", "As" + kind + "Statement", "As" + kind + "Node"}
|
|
69
|
+
if override, ok := asMethodOverrides[kind]; ok {
|
|
70
|
+
candidates = append([]string{override}, candidates...)
|
|
71
|
+
}
|
|
72
|
+
for _, candidate := range candidates {
|
|
73
|
+
meth := nodeVal.MethodByName(candidate)
|
|
74
|
+
if !meth.IsValid() {
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
results := meth.Call(nil)
|
|
78
|
+
if len(results) == 0 {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
v := results[0]
|
|
82
|
+
if v.IsNil() {
|
|
83
|
+
return reflect.Value{}
|
|
84
|
+
}
|
|
85
|
+
return v.Elem()
|
|
86
|
+
}
|
|
87
|
+
return reflect.Value{}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// serializeNode converts an ast.Node into a map: {kind, text?, <spec fields>}.
|
|
91
|
+
// inProgress guards against cyclic *Node references (self/parent backlinks).
|
|
92
|
+
func serializeNode(n *ast.Node) any {
|
|
93
|
+
if n == nil {
|
|
94
|
+
return nil
|
|
95
|
+
}
|
|
96
|
+
if inProgress[n] {
|
|
97
|
+
return nil
|
|
98
|
+
}
|
|
99
|
+
inProgress[n] = true
|
|
100
|
+
defer delete(inProgress, n)
|
|
101
|
+
obj := map[string]any{"kind": strings.TrimPrefix(n.Kind.String(), "Kind")}
|
|
102
|
+
// SourceFile bookkeeping fields are not part of the language AST
|
|
103
|
+
if n.Kind == ast.KindSourceFile {
|
|
104
|
+
statements := []any{}
|
|
105
|
+
for _, stmt := range n.AsSourceFile().Statements.Nodes {
|
|
106
|
+
if stmt != nil {
|
|
107
|
+
statements = append(statements, serializeNode(stmt))
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
obj["statements"] = statements
|
|
111
|
+
return obj
|
|
112
|
+
}
|
|
113
|
+
// Names and modifiers live in unexported base fields — use accessors
|
|
114
|
+
if name := n.Name(); name != nil {
|
|
115
|
+
obj["name"] = serializeNode(name.AsNode())
|
|
116
|
+
}
|
|
117
|
+
if mods := n.Modifiers(); mods != nil {
|
|
118
|
+
arr := []any{}
|
|
119
|
+
for _, mod := range mods.Nodes {
|
|
120
|
+
arr = append(arr, serializeNode(mod))
|
|
121
|
+
}
|
|
122
|
+
obj["modifiers"] = arr
|
|
123
|
+
}
|
|
124
|
+
switch n.Kind {
|
|
125
|
+
case ast.KindIdentifier, ast.KindPrivateIdentifier, ast.KindStringLiteral,
|
|
126
|
+
ast.KindNumericLiteral, ast.KindBigIntLiteral, ast.KindNoSubstitutionTemplateLiteral,
|
|
127
|
+
ast.KindRegularExpressionLiteral, ast.KindTemplateHead, ast.KindTemplateMiddle,
|
|
128
|
+
ast.KindTemplateTail, ast.KindJsxText:
|
|
129
|
+
obj["text"] = n.Text()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Binding-pattern holes (`[a, , b]`): ts-go parses them as an empty
|
|
133
|
+
// BindingElement; the TypeScript API exposes them as OmittedExpression
|
|
134
|
+
if n.Kind == ast.KindBindingElement {
|
|
135
|
+
if be := n.AsBindingElement(); be != nil && be.Name() == nil && be.DotDotDotToken == nil {
|
|
136
|
+
return map[string]any{"kind": "OmittedExpression"}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
data := asData(n)
|
|
141
|
+
if data.IsValid() {
|
|
142
|
+
collectFields(data, obj)
|
|
143
|
+
}
|
|
144
|
+
return obj
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// collectFields serializes a typed data struct's fields into obj, recursing
|
|
148
|
+
// into embedded base structs (DeclarationBase, FunctionLikeBase, ...) so their
|
|
149
|
+
// fields are hoisted like the TypeScript API exposes them.
|
|
150
|
+
func collectFields(data reflect.Value, obj map[string]any) {
|
|
151
|
+
dt := data.Type()
|
|
152
|
+
for i := 0; i < dt.NumField(); i++ {
|
|
153
|
+
field := dt.Field(i)
|
|
154
|
+
if !field.IsExported() {
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
// Embedded base structs are hoisted — except NodeDefault/Node, whose
|
|
158
|
+
// fields (Kind/Flags/Loc/Parent) are parser bookkeeping, not AST
|
|
159
|
+
if field.Anonymous && field.Type.Kind() == reflect.Struct {
|
|
160
|
+
if field.Type.Name() != "NodeDefault" && field.Type.Name() != "Node" {
|
|
161
|
+
collectFields(data.Field(i), obj)
|
|
162
|
+
}
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
name, val, ok := serializeField(field.Name, data.Field(i))
|
|
166
|
+
if ok {
|
|
167
|
+
obj[name] = val
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// serializeField normalizes one typed-struct field into the TS-spec JSON shape.
|
|
173
|
+
func serializeField(name string, v reflect.Value) (string, any, bool) {
|
|
174
|
+
if renamed, ok := fieldRenames[name]; ok {
|
|
175
|
+
name = renamed
|
|
176
|
+
} else {
|
|
177
|
+
name = lowerFirst(name)
|
|
178
|
+
}
|
|
179
|
+
// Never emit parser bookkeeping fields from the embedded ast.Node
|
|
180
|
+
switch name {
|
|
181
|
+
case "kind", "flags", "loc", "parent", "id", "data":
|
|
182
|
+
return "", nil, false
|
|
183
|
+
}
|
|
184
|
+
switch v.Kind() {
|
|
185
|
+
case reflect.String:
|
|
186
|
+
return lowerFirst(name), v.String(), true
|
|
187
|
+
case reflect.Bool:
|
|
188
|
+
return lowerFirst(name), v.Bool(), true
|
|
189
|
+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
190
|
+
// Operator/Token fields hold token kinds; emit the kind name (matches the
|
|
191
|
+
// Node producer, which converts ts.SyntaxKind numbers to names)
|
|
192
|
+
if name == "operator" || name == "token" { // prelude lowercased the field name
|
|
193
|
+
kind := ast.Kind(v.Int())
|
|
194
|
+
return lowerFirst(name), strings.TrimPrefix(kind.String(), "Kind"), true
|
|
195
|
+
}
|
|
196
|
+
return "", nil, false
|
|
197
|
+
case reflect.Ptr:
|
|
198
|
+
if v.IsNil() {
|
|
199
|
+
return "", nil, false
|
|
200
|
+
}
|
|
201
|
+
if isNodePtr(v.Type()) {
|
|
202
|
+
return lowerFirst(name), serializeNode(v.Interface().(*ast.Node)), true
|
|
203
|
+
}
|
|
204
|
+
// *NodeBody / *EntityName etc. are pointer-to-interface wrappers
|
|
205
|
+
// whose dynamic value is a *Node
|
|
206
|
+
if v.Type().Elem().Kind() == reflect.Interface {
|
|
207
|
+
inner := v.Elem().Elem()
|
|
208
|
+
if inner.IsValid() && inner.Kind() == reflect.Ptr && !inner.IsNil() {
|
|
209
|
+
return lowerFirst(name), serializeNode(inner.Interface().(*ast.Node)), true
|
|
210
|
+
}
|
|
211
|
+
return "", nil, false
|
|
212
|
+
}
|
|
213
|
+
// e.g. *StatementList/*ArgumentList/*ModifierList → array of member
|
|
214
|
+
// nodes, named after the field (statements/arguments/modifiers)
|
|
215
|
+
if strings.HasSuffix(v.Type().Elem().Name(), "List") {
|
|
216
|
+
var arr []any
|
|
217
|
+
elem := v.Elem()
|
|
218
|
+
nodesField := elem.FieldByName("Nodes")
|
|
219
|
+
if nodesField.IsValid() && nodesField.Kind() == reflect.Slice {
|
|
220
|
+
for _, child := range iterSlice(nodesField) {
|
|
221
|
+
if child.Kind() == reflect.Ptr && isNodePtr(child.Type()) && !child.IsNil() {
|
|
222
|
+
arr = append(arr, serializeNode(child.Interface().(*ast.Node)))
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if len(arr) == 0 {
|
|
227
|
+
return "", nil, false
|
|
228
|
+
}
|
|
229
|
+
return lowerFirst(name), arr, true
|
|
230
|
+
}
|
|
231
|
+
return "", nil, false
|
|
232
|
+
case reflect.Slice:
|
|
233
|
+
var arr []any
|
|
234
|
+
for _, el := range iterSlice(v) {
|
|
235
|
+
if el.Kind() == reflect.Ptr && isNodePtr(el.Type()) && !el.IsNil() {
|
|
236
|
+
arr = append(arr, serializeNode(el.Interface().(*ast.Node)))
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if arr == nil {
|
|
240
|
+
arr = []any{}
|
|
241
|
+
}
|
|
242
|
+
return lowerFirst(name), arr, true
|
|
243
|
+
case reflect.Interface:
|
|
244
|
+
if v.IsNil() {
|
|
245
|
+
return "", nil, false
|
|
246
|
+
}
|
|
247
|
+
// Expression/Statement wrappers hold *Node underneath
|
|
248
|
+
inner := v.Elem()
|
|
249
|
+
if inner.Kind() == reflect.Ptr && isNodePtr(inner.Type()) && !inner.IsNil() {
|
|
250
|
+
return lowerFirst(name), serializeNode(inner.Interface().(*ast.Node)), true
|
|
251
|
+
}
|
|
252
|
+
return "", nil, false
|
|
253
|
+
}
|
|
254
|
+
return "", nil, false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
func isNodePtr(t reflect.Type) bool {
|
|
258
|
+
return t == reflect.TypeOf((*ast.Node)(nil))
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
func iterSlice(v reflect.Value) []reflect.Value {
|
|
262
|
+
out := make([]reflect.Value, 0, v.Len())
|
|
263
|
+
for i := 0; i < v.Len(); i++ {
|
|
264
|
+
out = append(out, v.Index(i))
|
|
265
|
+
}
|
|
266
|
+
return out
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
func lowerFirst(s string) string {
|
|
270
|
+
if s == "" {
|
|
271
|
+
return s
|
|
272
|
+
}
|
|
273
|
+
return strings.ToLower(s[:1]) + s[1:]
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Field renames to match the TypeScript API's names.
|
|
277
|
+
var fieldRenames = map[string]string{
|
|
278
|
+
"PostfixToken": "questionToken", // optional property `a?: string`
|
|
279
|
+
}
|
|
@@ -2,4 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
declare module 'node:path' {
|
|
4
4
|
export function join(...parts: string[]): string;
|
|
5
|
+
export function dirname(p: string): string;
|
|
6
|
+
export function basename(p: string, ext?: string): string;
|
|
7
|
+
export function extname(p: string): string;
|
|
8
|
+
export function resolve(...parts: string[]): string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module 'node:fs' {
|
|
12
|
+
export function readFileSync(path: string): string;
|
|
13
|
+
export function writeFileSync(path: string, content: string): void;
|
|
14
|
+
export function appendFileSync(path: string, content: string): void;
|
|
15
|
+
export function existsSync(path: string): boolean;
|
|
16
|
+
export function mkdirSync(path: string, options?: { recursive?: boolean }): void;
|
|
17
|
+
export function readdirSync(path: string): string[];
|
|
18
|
+
export function copyFileSync(src: string, dest: string): void;
|
|
19
|
+
export function rmSync(path: string, options?: { recursive?: boolean }): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module 'node:url' {
|
|
23
|
+
export function fileURLToPath(url: string): string;
|
|
24
|
+
export function pathToFileURL(path: string): string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare module 'node:os' {
|
|
28
|
+
export function platform(): string;
|
|
29
|
+
export function homedir(): string;
|
|
30
|
+
export function tmpdir(): string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare module 'node:child_process' {
|
|
34
|
+
export interface ExecResult {
|
|
35
|
+
stdout: string;
|
|
36
|
+
stderr: string;
|
|
37
|
+
status: number;
|
|
38
|
+
}
|
|
39
|
+
export function exec(command: string): ExecResult;
|
|
40
|
+
export function execSync(command: string): string;
|
|
41
|
+
export function spawnSync(file: string, args: string[], options?: object): ExecResult;
|
|
42
|
+
export function spawnInherit(file: string, ...args: string[]): number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare module 'node:readline' {
|
|
46
|
+
// TypeNative extension: synchronous line reading (mapped to a Go helper).
|
|
47
|
+
// Not part of Node's real readline API — used by self-hosted prompts.
|
|
48
|
+
export function question(prompt: string): string;
|
|
5
49
|
}
|