ttsc 0.11.0 → 0.12.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/README.md +25 -6
- package/cmd/ttsc/api_compile.go +101 -106
- package/cmd/ttsc/api_compile_branches_test.go +81 -0
- package/cmd/ttsc/api_transform.go +49 -54
- package/cmd/ttsc/api_transform_branches_test.go +41 -0
- package/cmd/ttsc/build.go +108 -109
- package/cmd/ttsc/build_branches_test.go +130 -0
- package/cmd/ttsc/helpers_test.go +47 -0
- package/cmd/ttsc/main.go +104 -103
- package/cmd/ttsc/main_alias_branches_test.go +29 -0
- package/cmd/ttscserver/main.go +104 -96
- package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +42 -0
- package/cmd/ttscserver/run_lsp_reports_server_error_test.go +23 -25
- package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +44 -0
- package/cmd/utility-host/main.go +42 -0
- package/driver/lsp.go +60 -0
- package/driver/plugins.go +122 -0
- package/driver/program.go +346 -356
- package/driver/rewrite.go +336 -339
- package/{driver → internal/lspserver}/lsp_envelope.go +70 -64
- package/{driver → internal/lspserver}/lsp_frame.go +62 -62
- package/{driver → internal/lspserver}/lsp_plugin.go +58 -58
- package/internal/lspserver/lsp_proxy.go +391 -0
- package/internal/lspserver/lsp_server.go +231 -0
- package/lib/TtscCompiler.d.ts +3 -3
- package/lib/TtscCompiler.js +20 -6
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/resolveTsgo.d.ts +1 -0
- package/lib/compiler/internal/resolveTsgo.js +15 -4
- package/lib/compiler/internal/resolveTsgo.js.map +1 -1
- package/lib/compiler/internal/runBuild.js +12 -4
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +12 -33
- package/lib/compiler/internal/sharedHostHelpers.js +27 -70
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +12 -5
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +9 -4
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.d.ts +1 -0
- package/lib/launcher/internal/runTtscserver.js +38 -1
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +3 -0
- package/lib/plugin/internal/buildSourcePlugin.js +444 -41
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +153 -34
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerContext.d.ts +2 -1
- package/lib/structures/ITtscPlugin.d.ts +8 -13
- package/lib/structures/ITtscPluginFactoryContext.d.ts +4 -5
- package/lib/structures/ITtscProjectPluginConfig.d.ts +2 -2
- package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +4 -2
- package/package.json +8 -8
- package/shim/lsp/shim.go +11 -17
- package/src/TtscCompiler.ts +20 -7
- package/src/compiler/internal/resolveTsgo.ts +18 -5
- package/src/compiler/internal/runBuild.ts +17 -4
- package/src/compiler/internal/sharedHostHelpers.ts +34 -67
- package/src/compiler/internal/transformProjectInMemory.ts +18 -5
- package/src/index.ts +0 -1
- package/src/launcher/internal/runTtsc.ts +9 -4
- package/src/launcher/internal/runTtscserver.ts +43 -1
- package/src/plugin/internal/buildSourcePlugin.ts +475 -41
- package/src/plugin/internal/loadProjectPlugins.ts +199 -39
- package/src/structures/ITtscCompilerContext.ts +2 -1
- package/src/structures/ITtscPlugin.ts +9 -13
- package/src/structures/ITtscPluginFactoryContext.ts +4 -5
- package/src/structures/ITtscProjectPluginConfig.ts +2 -2
- package/src/structures/internal/ITtscLoadedNativePlugin.ts +4 -2
- package/utility/host.go +267 -1296
- package/driver/lsp_proxy.go +0 -394
- package/driver/lsp_server.go +0 -197
- package/lib/defineTtscPlugin.d.ts +0 -18
- package/lib/defineTtscPlugin.js +0 -7
- package/lib/defineTtscPlugin.js.map +0 -1
- package/src/defineTtscPlugin.ts +0 -26
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Verifies main command aliases cover flag-shaped demo and cts project inputs.
|
|
7
|
+
*
|
|
8
|
+
* The top-level dispatcher treats extension-shaped arguments as build aliases
|
|
9
|
+
* while demo owns its own flag parser. These small branches keep compatibility
|
|
10
|
+
* with `ttsc ./tsconfig.cts`-style invocations and malformed demo flags.
|
|
11
|
+
*
|
|
12
|
+
* 1. Run demo with a missing flag value.
|
|
13
|
+
* 2. Dispatch a `.cts` argument through the build alias path.
|
|
14
|
+
* 3. Assert both routes fail through their intended parser.
|
|
15
|
+
*/
|
|
16
|
+
func TestMainAliasBranches(t *testing.T) {
|
|
17
|
+
code, _, _ := captureCommand(t, func() int {
|
|
18
|
+
return run([]string{"demo", "--type"})
|
|
19
|
+
})
|
|
20
|
+
if code != 2 {
|
|
21
|
+
t.Fatalf("demo flag status mismatch: %d", code)
|
|
22
|
+
}
|
|
23
|
+
code, _, _ = captureCommand(t, func() int {
|
|
24
|
+
return run([]string{"project.cts"})
|
|
25
|
+
})
|
|
26
|
+
if code != 2 {
|
|
27
|
+
t.Fatalf("cts build alias status mismatch: %d", code)
|
|
28
|
+
}
|
|
29
|
+
}
|
package/cmd/ttscserver/main.go
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
// Command ttscserver is the Go LSP host shipped by ttsc. It
|
|
2
|
-
// TypeScript-Go
|
|
3
|
-
// editor and that server, splicing ttsc-plugin diagnostics,
|
|
4
|
-
// and ttsc-owned executeCommand handling into the same stream.
|
|
1
|
+
// Command ttscserver is the Go LSP host shipped by ttsc. It wraps the
|
|
2
|
+
// project-selected TypeScript-Go LSP server process and proxies traffic
|
|
3
|
+
// between the editor and that server, splicing ttsc-plugin diagnostics,
|
|
4
|
+
// code actions, and ttsc-owned executeCommand handling into the same stream.
|
|
5
5
|
//
|
|
6
6
|
// The JavaScript launcher (`packages/ttsc/src/launcher/ttscserver.ts`)
|
|
7
7
|
// resolves the native binary and forwards stdio so editors can spawn
|
|
8
8
|
// `ttscserver --stdio` without worrying about platform helper packages.
|
|
9
9
|
//
|
|
10
10
|
// Everything here is deliberately small: flag parsing, version metadata,
|
|
11
|
-
// and a single delegation to
|
|
11
|
+
// and a single delegation to lspserver.RunLSPServer.
|
|
12
12
|
package main
|
|
13
13
|
|
|
14
14
|
import (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
"context"
|
|
16
|
+
"errors"
|
|
17
|
+
"flag"
|
|
18
|
+
"fmt"
|
|
19
|
+
"io"
|
|
20
|
+
"os"
|
|
21
|
+
"os/signal"
|
|
22
|
+
"runtime"
|
|
23
|
+
"strings"
|
|
24
|
+
"syscall"
|
|
25
|
+
"time"
|
|
26
|
+
|
|
27
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
// Build metadata; overwritten via -ldflags in release builds.
|
|
31
31
|
var (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
version = "0.0.0-dev"
|
|
33
|
+
commit = "dev"
|
|
34
|
+
date = "unknown"
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
// Package-level writers so command tests can capture output without
|
|
38
38
|
// patching os.Stdout / os.Stderr globally.
|
|
39
39
|
var (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
stdout io.Writer = os.Stdout
|
|
41
|
+
stderr io.Writer = os.Stderr
|
|
42
|
+
stdin io.Reader = os.Stdin
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
// runLSPServer is the seam command tests use to substitute a fake LSP
|
|
46
|
-
// host. Production wires it to
|
|
47
|
-
var runLSPServer =
|
|
46
|
+
// host. Production wires it to lspserver.RunLSPServer.
|
|
47
|
+
var runLSPServer = lspserver.RunLSPServer
|
|
48
48
|
|
|
49
49
|
// notifyContext is the seam command tests use to substitute a
|
|
50
50
|
// deterministic context (no signal hookup) for the signal-aware default.
|
|
@@ -56,82 +56,89 @@ var notifyContext = signal.NotifyContext
|
|
|
56
56
|
var getwd = os.Getwd
|
|
57
57
|
|
|
58
58
|
func main() {
|
|
59
|
-
|
|
59
|
+
os.Exit(run(os.Args[1:]))
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
func run(args []string) int {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
if len(args) == 0 {
|
|
64
|
+
printHelp(stdout)
|
|
65
|
+
return 0
|
|
66
|
+
}
|
|
67
|
+
switch args[0] {
|
|
68
|
+
case "-h", "--help", "help":
|
|
69
|
+
printHelp(stdout)
|
|
70
|
+
return 0
|
|
71
|
+
case "-v", "--version", "version":
|
|
72
|
+
printVersion(stdout)
|
|
73
|
+
return 0
|
|
74
|
+
}
|
|
75
|
+
return runLSP(args)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
func runLSP(args []string) int {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
79
|
+
fs := flag.NewFlagSet("ttscserver", flag.ContinueOnError)
|
|
80
|
+
fs.SetOutput(stderr)
|
|
81
|
+
stdioFlag := fs.Bool("stdio", false, "communicate with the editor over stdin/stdout")
|
|
82
|
+
cwdFlag := fs.String("cwd", "", "project root (defaults to process cwd)")
|
|
83
|
+
tsgoFlag := fs.String("tsgo", "", "absolute tsgo binary path (defaults to TTSC_TSGO_BINARY)")
|
|
84
|
+
progressDelayFlag := fs.Duration("progress-delay", 250*time.Millisecond, "delay before showing tsgo's progress UI")
|
|
85
|
+
if err := fs.Parse(args); err != nil {
|
|
86
|
+
return 2
|
|
87
|
+
}
|
|
88
|
+
if !*stdioFlag {
|
|
89
|
+
fmt.Fprintln(stderr, "ttscserver: only --stdio transport is supported")
|
|
90
|
+
return 2
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
cwd := strings.TrimSpace(*cwdFlag)
|
|
94
|
+
if cwd == "" {
|
|
95
|
+
resolved, err := getwd()
|
|
96
|
+
if err != nil {
|
|
97
|
+
fmt.Fprintf(stderr, "ttscserver: could not resolve working directory: %v\n", err)
|
|
98
|
+
return 2
|
|
99
|
+
}
|
|
100
|
+
cwd = resolved
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ctx, stop := notifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
104
|
+
defer stop()
|
|
105
|
+
|
|
106
|
+
tsgoBinary := strings.TrimSpace(*tsgoFlag)
|
|
107
|
+
if tsgoBinary == "" {
|
|
108
|
+
tsgoBinary = strings.TrimSpace(os.Getenv("TTSC_TSGO_BINARY"))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
err := runLSPServer(ctx, lspserver.LSPServerOptions{
|
|
112
|
+
In: stdin,
|
|
113
|
+
Out: stdout,
|
|
114
|
+
Err: stderr,
|
|
115
|
+
Cwd: cwd,
|
|
116
|
+
TsgoBinary: tsgoBinary,
|
|
117
|
+
Source: lspserver.NullPluginSource{},
|
|
118
|
+
ProgressDelay: *progressDelayFlag,
|
|
119
|
+
})
|
|
120
|
+
if err != nil && !errors.Is(err, context.Canceled) {
|
|
121
|
+
fmt.Fprintf(stderr, "ttscserver: %v\n", err)
|
|
122
|
+
return 1
|
|
123
|
+
}
|
|
124
|
+
return 0
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
func printVersion(w io.Writer) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
fmt.Fprintf(
|
|
129
|
+
w,
|
|
130
|
+
"ttscserver %s (commit %s, built %s, %s/%s, go %s)\n",
|
|
131
|
+
version,
|
|
132
|
+
commit,
|
|
133
|
+
date,
|
|
134
|
+
runtime.GOOS,
|
|
135
|
+
runtime.GOARCH,
|
|
136
|
+
runtime.Version(),
|
|
137
|
+
)
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
func printHelp(w io.Writer) {
|
|
134
|
-
|
|
141
|
+
fmt.Fprintln(w, strings.TrimSpace(`
|
|
135
142
|
ttscserver — Language Server Protocol host for ttsc.
|
|
136
143
|
|
|
137
144
|
Usage:
|
|
@@ -141,14 +148,15 @@ Usage:
|
|
|
141
148
|
|
|
142
149
|
Options:
|
|
143
150
|
--stdio Communicate with the editor over stdin/stdout.
|
|
144
|
-
--cwd <dir> Project root
|
|
145
|
-
--
|
|
151
|
+
--cwd <dir> Project root used as the tsgo server working directory.
|
|
152
|
+
--tsgo <path> Absolute tsgo binary path (defaults to TTSC_TSGO_BINARY).
|
|
153
|
+
--progress-delay D Accepted for compatibility; tsgo currently owns this value.
|
|
146
154
|
|
|
147
155
|
Typical embedding:
|
|
148
156
|
Editors spawn ttscserver via the JavaScript launcher (resolves the
|
|
149
|
-
per-platform native binary
|
|
150
|
-
|
|
151
|
-
ttsc merges plugin
|
|
152
|
-
owned executeCommand handlers into the same stream.
|
|
157
|
+
per-platform native binary and passes the project tsgo path) and exchange
|
|
158
|
+
LSP messages over stdio. The upstream tsgo server provides hover,
|
|
159
|
+
completion, definitions, and diagnostics; ttsc merges plugin diagnostics,
|
|
160
|
+
code actions, and ttsc-owned executeCommand handlers into the same stream.
|
|
153
161
|
`))
|
|
154
162
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestRunLSPPrefersTsgoFlag verifies the native command forwards an explicit
|
|
12
|
+
// upstream tsgo path into the LSP host options.
|
|
13
|
+
//
|
|
14
|
+
// Editors normally enter through the JavaScript launcher, but direct native
|
|
15
|
+
// callers can pass --tsgo. This pins that flag path separately from the
|
|
16
|
+
// TTSC_TSGO_BINARY environment fallback.
|
|
17
|
+
//
|
|
18
|
+
// 1. Substitute the runLSPServer seam and capture its options.
|
|
19
|
+
// 2. Run runLSP with --stdio, --cwd, and --tsgo.
|
|
20
|
+
// 3. Assert the captured TsgoBinary is the flag value.
|
|
21
|
+
func TestRunLSPPrefersTsgoFlag(t *testing.T) {
|
|
22
|
+
const expected = "/tmp/tsgo-test-binary"
|
|
23
|
+
prev := runLSPServer
|
|
24
|
+
var captured lspserver.LSPServerOptions
|
|
25
|
+
runLSPServer = func(_ context.Context, opts lspserver.LSPServerOptions) error {
|
|
26
|
+
captured = opts
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
defer func() { runLSPServer = prev }()
|
|
30
|
+
|
|
31
|
+
outBuf := &bytes.Buffer{}
|
|
32
|
+
errBuf := &bytes.Buffer{}
|
|
33
|
+
withIO(t, outBuf, errBuf, nil, func() {
|
|
34
|
+
if code := runLSP([]string{"--stdio", "--cwd", t.TempDir(), "--tsgo", expected}); code != 0 {
|
|
35
|
+
t.Fatalf("expected exit 0, got %d (stderr=%q)", code, errBuf.String())
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if captured.TsgoBinary != expected {
|
|
40
|
+
t.Fatalf("expected TsgoBinary %q, got %q", expected, captured.TsgoBinary)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"testing"
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"errors"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
|
|
12
11
|
)
|
|
13
12
|
|
|
14
13
|
// TestRunLSPReportsServerError pins the non-cancellation error path in
|
|
15
|
-
// runLSP. When RunLSPServer reports a real failure (the
|
|
14
|
+
// runLSP. When RunLSPServer reports a real failure (the upstream server
|
|
16
15
|
// crashed, an unrecoverable IO error, etc.) the launcher must exit 1
|
|
17
16
|
// and copy the error message to stderr.
|
|
18
17
|
//
|
|
@@ -20,23 +19,22 @@ import (
|
|
|
20
19
|
// 2. Run runLSP with --stdio --cwd <tempdir>.
|
|
21
20
|
// 3. Assert exit 1 and the sentinel text on stderr.
|
|
22
21
|
func TestRunLSPReportsServerError(t *testing.T) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
sentinel := errors.New("lsp host blew up")
|
|
23
|
+
prev := runLSPServer
|
|
24
|
+
runLSPServer = func(_ context.Context, _ lspserver.LSPServerOptions) error {
|
|
25
|
+
return sentinel
|
|
26
|
+
}
|
|
27
|
+
defer func() { runLSPServer = prev }()
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
outBuf := &bytes.Buffer{}
|
|
30
|
+
errBuf := &bytes.Buffer{}
|
|
31
|
+
withIO(t, outBuf, errBuf, nil, func() {
|
|
32
|
+
if code := runLSP([]string{"--stdio", "--cwd", t.TempDir()}); code != 1 {
|
|
33
|
+
t.Fatalf("expected exit 1, got %d", code)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
_ = io.Discard
|
|
37
|
+
if !strings.Contains(errBuf.String(), sentinel.Error()) {
|
|
38
|
+
t.Fatalf("expected sentinel on stderr, got: %q", errBuf.String())
|
|
39
|
+
}
|
|
42
40
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestRunLSPUsesTsgoEnvironment verifies TTSC_TSGO_BINARY feeds the native host
|
|
12
|
+
// when no --tsgo flag is present.
|
|
13
|
+
//
|
|
14
|
+
// The JavaScript launcher resolves @typescript/native-preview and passes the
|
|
15
|
+
// absolute path through this environment variable, keeping the Go binary thin
|
|
16
|
+
// and independent of Node's module resolver.
|
|
17
|
+
//
|
|
18
|
+
// 1. Set TTSC_TSGO_BINARY to a sentinel path.
|
|
19
|
+
// 2. Substitute runLSPServer and capture its options.
|
|
20
|
+
// 3. Assert TsgoBinary is populated from the environment.
|
|
21
|
+
func TestRunLSPUsesTsgoEnvironment(t *testing.T) {
|
|
22
|
+
const expected = "/tmp/tsgo-env-binary"
|
|
23
|
+
t.Setenv("TTSC_TSGO_BINARY", expected)
|
|
24
|
+
|
|
25
|
+
prev := runLSPServer
|
|
26
|
+
var captured lspserver.LSPServerOptions
|
|
27
|
+
runLSPServer = func(_ context.Context, opts lspserver.LSPServerOptions) error {
|
|
28
|
+
captured = opts
|
|
29
|
+
return nil
|
|
30
|
+
}
|
|
31
|
+
defer func() { runLSPServer = prev }()
|
|
32
|
+
|
|
33
|
+
outBuf := &bytes.Buffer{}
|
|
34
|
+
errBuf := &bytes.Buffer{}
|
|
35
|
+
withIO(t, outBuf, errBuf, nil, func() {
|
|
36
|
+
if code := runLSP([]string{"--stdio", "--cwd", t.TempDir()}); code != 0 {
|
|
37
|
+
t.Fatalf("expected exit 0, got %d (stderr=%q)", code, errBuf.String())
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if captured.TsgoBinary != expected {
|
|
42
|
+
t.Fatalf("expected TsgoBinary %q, got %q", expected, captured.TsgoBinary)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
|
|
7
|
+
"github.com/samchon/ttsc/packages/ttsc/utility"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func main() {
|
|
11
|
+
os.Exit(run(os.Args[1:]))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func run(args []string) int {
|
|
15
|
+
if len(args) == 0 {
|
|
16
|
+
return utility.RunBuild(nil)
|
|
17
|
+
}
|
|
18
|
+
switch args[0] {
|
|
19
|
+
case "build":
|
|
20
|
+
return utility.RunBuild(args[1:])
|
|
21
|
+
case "check":
|
|
22
|
+
return utility.RunCheck(args[1:])
|
|
23
|
+
case "transform":
|
|
24
|
+
return utility.RunTransform(args[1:])
|
|
25
|
+
case "-h", "--help", "help":
|
|
26
|
+
printHelp()
|
|
27
|
+
return 0
|
|
28
|
+
default:
|
|
29
|
+
fmt.Fprintf(os.Stderr, "ttsc utility: unknown command %q\n", args[0])
|
|
30
|
+
fmt.Fprintln(os.Stderr, `ttsc utility: run "help" to see supported commands`)
|
|
31
|
+
return 2
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func printHelp() {
|
|
36
|
+
fmt.Fprintln(os.Stdout, `ttsc utility host
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
utility-host build --tsconfig=tsconfig.json --plugins-json='[...]'
|
|
40
|
+
utility-host check --tsconfig=tsconfig.json --plugins-json='[...]'
|
|
41
|
+
utility-host transform --tsconfig=tsconfig.json --plugins-json='[...]'`)
|
|
42
|
+
}
|
package/driver/lsp.go
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
|
|
6
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type LSPPosition = lspserver.LSPPosition
|
|
10
|
+
type LSPRange = lspserver.LSPRange
|
|
11
|
+
type LSPDiagnosticSeverity = lspserver.LSPDiagnosticSeverity
|
|
12
|
+
|
|
13
|
+
const (
|
|
14
|
+
LSPDiagnosticSeverityError = lspserver.LSPDiagnosticSeverityError
|
|
15
|
+
LSPDiagnosticSeverityWarning = lspserver.LSPDiagnosticSeverityWarning
|
|
16
|
+
LSPDiagnosticSeverityInformation = lspserver.LSPDiagnosticSeverityInformation
|
|
17
|
+
LSPDiagnosticSeverityHint = lspserver.LSPDiagnosticSeverityHint
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
type LSPDiagnostic = lspserver.LSPDiagnostic
|
|
21
|
+
type LSPCodeAction = lspserver.LSPCodeAction
|
|
22
|
+
type LSPCommand = lspserver.LSPCommand
|
|
23
|
+
type LSPCodeActionContext = lspserver.LSPCodeActionContext
|
|
24
|
+
type LSPWorkspaceEdit = lspserver.LSPWorkspaceEdit
|
|
25
|
+
type LSPTextEdit = lspserver.LSPTextEdit
|
|
26
|
+
type LSPDocumentVersion = lspserver.LSPDocumentVersion
|
|
27
|
+
type PluginSource = lspserver.PluginSource
|
|
28
|
+
type NullPluginSource = lspserver.NullPluginSource
|
|
29
|
+
|
|
30
|
+
type ProxyOptions = lspserver.ProxyOptions
|
|
31
|
+
type Proxy = lspserver.Proxy
|
|
32
|
+
|
|
33
|
+
type FrameReader = lspserver.FrameReader
|
|
34
|
+
type Envelope = lspserver.Envelope
|
|
35
|
+
|
|
36
|
+
type LSPServerOptions = lspserver.LSPServerOptions
|
|
37
|
+
type LSPUpstreamRunner = lspserver.LSPUpstreamRunner
|
|
38
|
+
|
|
39
|
+
const MaxFrameBytes = lspserver.MaxFrameBytes
|
|
40
|
+
|
|
41
|
+
var ErrCommandNotHandled = lspserver.ErrCommandNotHandled
|
|
42
|
+
var ErrFrameClosed = lspserver.ErrFrameClosed
|
|
43
|
+
var ErrFrameTooLarge = lspserver.ErrFrameTooLarge
|
|
44
|
+
var ErrInvalidJSONRPC = lspserver.ErrInvalidJSONRPC
|
|
45
|
+
var ErrLSPUpstreamPanic = lspserver.ErrLSPUpstreamPanic
|
|
46
|
+
var ErrLSPCwdRequired = lspserver.ErrLSPCwdRequired
|
|
47
|
+
var ErrLSPTsgoBinaryRequired = lspserver.ErrLSPTsgoBinaryRequired
|
|
48
|
+
|
|
49
|
+
var NewProxy = lspserver.NewProxy
|
|
50
|
+
var NewFrameReader = lspserver.NewFrameReader
|
|
51
|
+
var WriteFrame = lspserver.WriteFrame
|
|
52
|
+
var ParseEnvelope = lspserver.ParseEnvelope
|
|
53
|
+
var RecoverPanicAs = lspserver.RecoverPanicAs
|
|
54
|
+
var WithUpstreamRunnerForTest = lspserver.WithUpstreamRunnerForTest
|
|
55
|
+
var RunLSPServer = lspserver.RunLSPServer
|
|
56
|
+
var DenyNpmInstall = lspserver.DenyNpmInstall
|
|
57
|
+
|
|
58
|
+
func idKeyFromRaw(raw json.RawMessage) string {
|
|
59
|
+
return lspserver.IDKeyFromRaw(raw)
|
|
60
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
package driver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"strings"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
const LinkedPluginsEnv = "TTSC_LINKED_PLUGINS_JSON"
|
|
11
|
+
|
|
12
|
+
// PluginEntry is the manifest shape ttsc passes to driver-level plugins.
|
|
13
|
+
type PluginEntry struct {
|
|
14
|
+
Config map[string]any `json:"config"`
|
|
15
|
+
Name string `json:"name"`
|
|
16
|
+
Stage string `json:"stage"`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// PluginContext is the per-entry context passed to registered linked plugins.
|
|
20
|
+
type PluginContext struct {
|
|
21
|
+
Cwd string
|
|
22
|
+
Entry PluginEntry
|
|
23
|
+
Tsconfig string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// SourcePreamblePlugin can inject source text before TypeScript-Go parses the
|
|
27
|
+
// project. This is intentionally generic: the driver knows only the registered
|
|
28
|
+
// plugin name and the project plugin manifest.
|
|
29
|
+
type SourcePreamblePlugin interface {
|
|
30
|
+
SourcePreamble(PluginContext) (string, error)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ProgramPlugin can mutate a loaded Program before source output or emit.
|
|
34
|
+
type ProgramPlugin interface {
|
|
35
|
+
ApplyProgram(*Program, PluginContext) error
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type linkedPluginState struct {
|
|
39
|
+
cwd string
|
|
40
|
+
entries []PluginEntry
|
|
41
|
+
tsconfig string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var pluginRegistry []any
|
|
45
|
+
|
|
46
|
+
// RegisterPlugin registers a driver-level plugin implementation. Linked Go
|
|
47
|
+
// packages call this from init(); ttsc pairs registrations with linked manifest
|
|
48
|
+
// entries by build order, not by package name.
|
|
49
|
+
func RegisterPlugin(plugin any) {
|
|
50
|
+
if plugin == nil {
|
|
51
|
+
panic("driver: RegisterPlugin called with nil plugin")
|
|
52
|
+
}
|
|
53
|
+
pluginRegistry = append(pluginRegistry, plugin)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func loadLinkedPluginState(cwd, tsconfigPath string) (linkedPluginState, error) {
|
|
57
|
+
input := strings.TrimSpace(os.Getenv(LinkedPluginsEnv))
|
|
58
|
+
if input == "" {
|
|
59
|
+
return linkedPluginState{cwd: cwd, tsconfig: tsconfigPath}, nil
|
|
60
|
+
}
|
|
61
|
+
var entries []PluginEntry
|
|
62
|
+
if err := json.Unmarshal([]byte(input), &entries); err != nil {
|
|
63
|
+
return linkedPluginState{}, fmt.Errorf("ttsc driver: invalid %s: %w", LinkedPluginsEnv, err)
|
|
64
|
+
}
|
|
65
|
+
return linkedPluginState{
|
|
66
|
+
cwd: cwd,
|
|
67
|
+
entries: entries,
|
|
68
|
+
tsconfig: tsconfigPath,
|
|
69
|
+
}, nil
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func (state linkedPluginState) sourcePreamble() (string, error) {
|
|
73
|
+
var out strings.Builder
|
|
74
|
+
for index, entry := range state.entries {
|
|
75
|
+
plugin, ok := registeredPlugin(index)
|
|
76
|
+
if !ok {
|
|
77
|
+
return "", fmt.Errorf("ttsc driver: linked plugin entry %d was requested but no linked plugin registered at that position", index)
|
|
78
|
+
}
|
|
79
|
+
preamble, ok := plugin.(SourcePreamblePlugin)
|
|
80
|
+
if !ok {
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
text, err := preamble.SourcePreamble(state.context(entry))
|
|
84
|
+
if err != nil {
|
|
85
|
+
return "", err
|
|
86
|
+
}
|
|
87
|
+
out.WriteString(text)
|
|
88
|
+
}
|
|
89
|
+
return out.String(), nil
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func (state linkedPluginState) apply(prog *Program) error {
|
|
93
|
+
for index, entry := range state.entries {
|
|
94
|
+
plugin, ok := registeredPlugin(index)
|
|
95
|
+
if !ok {
|
|
96
|
+
return fmt.Errorf("ttsc driver: linked plugin entry %d was requested but no linked plugin registered at that position", index)
|
|
97
|
+
}
|
|
98
|
+
transform, ok := plugin.(ProgramPlugin)
|
|
99
|
+
if !ok {
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
if err := transform.ApplyProgram(prog, state.context(entry)); err != nil {
|
|
103
|
+
return err
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return nil
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func registeredPlugin(index int) (any, bool) {
|
|
110
|
+
if index < 0 || index >= len(pluginRegistry) {
|
|
111
|
+
return nil, false
|
|
112
|
+
}
|
|
113
|
+
return pluginRegistry[index], true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func (state linkedPluginState) context(entry PluginEntry) PluginContext {
|
|
117
|
+
return PluginContext{
|
|
118
|
+
Cwd: state.cwd,
|
|
119
|
+
Entry: entry,
|
|
120
|
+
Tsconfig: state.tsconfig,
|
|
121
|
+
}
|
|
122
|
+
}
|