@ttsc/strip 0.7.2 → 0.7.3
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/package.json +1 -1
- package/plugin/main.go +26 -19
- package/plugin/strip.go +317 -291
package/package.json
CHANGED
package/plugin/main.go
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
|
+
// Native sidecar entrypoint for `@ttsc/strip`.
|
|
2
|
+
//
|
|
3
|
+
// The sidecar implements an output-stage transform for JavaScript emit. It
|
|
4
|
+
// removes configured call statements and optional debugger statements after
|
|
5
|
+
// TypeScript-Go prints JS, leaving typechecking and project loading to ttsc.
|
|
1
6
|
package main
|
|
2
7
|
|
|
3
8
|
import (
|
|
4
|
-
|
|
5
|
-
|
|
9
|
+
"fmt"
|
|
10
|
+
"os"
|
|
6
11
|
)
|
|
7
12
|
|
|
8
13
|
const version = "0.0.1"
|
|
9
14
|
|
|
10
15
|
func main() {
|
|
11
|
-
|
|
16
|
+
os.Exit(run(os.Args[1:]))
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
func run(args []string) int {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
if len(args) == 0 {
|
|
21
|
+
fmt.Fprintln(os.Stderr, "@ttsc/strip: command required (expected output|version)")
|
|
22
|
+
return 2
|
|
23
|
+
}
|
|
24
|
+
switch args[0] {
|
|
25
|
+
case "-v", "--version", "version":
|
|
26
|
+
fmt.Fprintf(os.Stdout, "@ttsc/strip %s\n", version)
|
|
27
|
+
return 0
|
|
28
|
+
case "check":
|
|
29
|
+
// Strip rewriting is output-only; configuration is validated when output
|
|
30
|
+
// text is actually transformed.
|
|
31
|
+
return 0
|
|
32
|
+
case "output":
|
|
33
|
+
return RunOutput(args[1:])
|
|
34
|
+
default:
|
|
35
|
+
fmt.Fprintf(os.Stderr, "@ttsc/strip: unknown command %q\n", args[0])
|
|
36
|
+
return 2
|
|
37
|
+
}
|
|
31
38
|
}
|
package/plugin/strip.go
CHANGED
|
@@ -1,359 +1,385 @@
|
|
|
1
|
+
// Output transformer for `@ttsc/strip`.
|
|
2
|
+
//
|
|
3
|
+
// The transform parses emitted JavaScript, removes complete statements that
|
|
4
|
+
// match configured call patterns, and preserves all other text. It operates on
|
|
5
|
+
// JS output rather than TS source so the runtime file shape remains consistent
|
|
6
|
+
// with TypeScript-Go's printer.
|
|
1
7
|
package main
|
|
2
8
|
|
|
3
9
|
import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
"encoding/json"
|
|
11
|
+
"flag"
|
|
12
|
+
"fmt"
|
|
13
|
+
"os"
|
|
14
|
+
"path/filepath"
|
|
15
|
+
"sort"
|
|
16
|
+
"strings"
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
19
|
+
shimcore "github.com/microsoft/typescript-go/shim/core"
|
|
20
|
+
shimparser "github.com/microsoft/typescript-go/shim/parser"
|
|
15
21
|
)
|
|
16
22
|
|
|
17
23
|
type pluginEntry struct {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
// Config is the plugin-specific entry from compilerOptions.plugins[] once
|
|
25
|
+
// the sidecar locates the `@ttsc/strip` descriptor in the ordered manifest.
|
|
26
|
+
Config map[string]any `json:"config"`
|
|
27
|
+
Name string `json:"name"`
|
|
28
|
+
Stage string `json:"stage"`
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
type stripTransform struct {
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
// calls stores dotted call patterns such as console.log or invariant.*.
|
|
33
|
+
// stripDebugger controls DebuggerStatement removal independently.
|
|
34
|
+
calls []callPattern
|
|
35
|
+
stripDebugger bool
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
type callPattern struct {
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
// parts contains the dotted name split on '.'. wildcard means the final
|
|
40
|
+
// source token was '*' and therefore matches any deeper property call.
|
|
41
|
+
parts []string
|
|
42
|
+
wildcard bool
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
type textEdit struct {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
start int
|
|
47
|
+
end int
|
|
48
|
+
text string
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
func RunOutput(args []string) int {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
52
|
+
fs := flag.NewFlagSet("output", flag.ContinueOnError)
|
|
53
|
+
fs.SetOutput(os.Stderr)
|
|
54
|
+
file := fs.String("file", "", "emitted file to transform")
|
|
55
|
+
out := fs.String("out", "", "write transformed text to this file instead of updating --file")
|
|
56
|
+
_ = fs.String("cwd", "", "project directory")
|
|
57
|
+
_ = fs.String("outDir", "", "emit directory override")
|
|
58
|
+
pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
|
|
59
|
+
_ = fs.String("tsconfig", "tsconfig.json", "project tsconfig")
|
|
60
|
+
if err := fs.Parse(args); err != nil {
|
|
61
|
+
return 2
|
|
62
|
+
}
|
|
63
|
+
if *file == "" {
|
|
64
|
+
fmt.Fprintln(os.Stderr, "@ttsc/strip: output requires --file")
|
|
65
|
+
return 2
|
|
66
|
+
}
|
|
67
|
+
config, err := findConfig(*pluginsJSON)
|
|
68
|
+
if err != nil {
|
|
69
|
+
fmt.Fprintln(os.Stderr, err)
|
|
70
|
+
return 2
|
|
71
|
+
}
|
|
72
|
+
text, err := os.ReadFile(*file)
|
|
73
|
+
if err != nil {
|
|
74
|
+
fmt.Fprintf(os.Stderr, "@ttsc/strip: read %s: %v\n", *file, err)
|
|
75
|
+
return 2
|
|
76
|
+
}
|
|
77
|
+
patched, err := Apply(*file, string(text), config)
|
|
78
|
+
if err != nil {
|
|
79
|
+
fmt.Fprintln(os.Stderr, err)
|
|
80
|
+
return 2
|
|
81
|
+
}
|
|
82
|
+
target := *file
|
|
83
|
+
if *out != "" {
|
|
84
|
+
target = *out
|
|
85
|
+
}
|
|
86
|
+
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
|
87
|
+
fmt.Fprintf(os.Stderr, "@ttsc/strip: mkdir: %v\n", err)
|
|
88
|
+
return 2
|
|
89
|
+
}
|
|
90
|
+
if err := os.WriteFile(target, []byte(patched), 0o644); err != nil {
|
|
91
|
+
fmt.Fprintf(os.Stderr, "@ttsc/strip: write %s: %v\n", target, err)
|
|
92
|
+
return 2
|
|
93
|
+
}
|
|
94
|
+
return 0
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
func Apply(fileName string, text string, config map[string]any) (string, error) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
// Apply is deterministic and has no filesystem side effects. RunOutput owns
|
|
99
|
+
// file reads/writes so unit tests can exercise the transform directly.
|
|
100
|
+
strip, err := parseStrip(config)
|
|
101
|
+
if err != nil {
|
|
102
|
+
return "", err
|
|
103
|
+
}
|
|
104
|
+
return strip.apply(fileName, text)
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
func parseStrip(config map[string]any) (*stripTransform, error) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
108
|
+
calls, err := stringArrayConfig(config, "calls")
|
|
109
|
+
if err != nil {
|
|
110
|
+
return nil, fmt.Errorf("@ttsc/strip: %w", err)
|
|
111
|
+
}
|
|
112
|
+
statements, err := stringArrayConfig(config, "statements")
|
|
113
|
+
if err != nil {
|
|
114
|
+
return nil, fmt.Errorf("@ttsc/strip: %w", err)
|
|
115
|
+
}
|
|
116
|
+
out := &stripTransform{}
|
|
117
|
+
for _, call := range calls {
|
|
118
|
+
pattern, err := parseCallPattern(call)
|
|
119
|
+
if err != nil {
|
|
120
|
+
return nil, fmt.Errorf("@ttsc/strip: %w", err)
|
|
121
|
+
}
|
|
122
|
+
out.calls = append(out.calls, pattern)
|
|
123
|
+
}
|
|
124
|
+
for _, statement := range statements {
|
|
125
|
+
switch statement {
|
|
126
|
+
case "debugger":
|
|
127
|
+
out.stripDebugger = true
|
|
128
|
+
default:
|
|
129
|
+
return nil, fmt.Errorf("@ttsc/strip: unsupported statement pattern %q", statement)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out, nil
|
|
119
133
|
}
|
|
120
134
|
|
|
121
135
|
func (s *stripTransform) apply(fileName string, text string) (string, error) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
136
|
+
// Declaration files and maps are intentionally skipped. Strip patterns are
|
|
137
|
+
// JavaScript runtime statements, not type-level declarations.
|
|
138
|
+
if s == nil || !isJavaScriptOutput(fileName) || (len(s.calls) == 0 && !s.stripDebugger) {
|
|
139
|
+
return text, nil
|
|
140
|
+
}
|
|
141
|
+
file := parseJS(fileName, text)
|
|
142
|
+
if file == nil {
|
|
143
|
+
return text, nil
|
|
144
|
+
}
|
|
145
|
+
edits := make([]textEdit, 0)
|
|
146
|
+
// The AST tells us which statements are safe to remove; the original text is
|
|
147
|
+
// still used for editing so comments, whitespace, and unrelated printer
|
|
148
|
+
// details remain untouched.
|
|
149
|
+
var walk func(*shimast.Node)
|
|
150
|
+
walk = func(node *shimast.Node) {
|
|
151
|
+
if node == nil {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
switch node.Kind {
|
|
155
|
+
case shimast.KindDebuggerStatement:
|
|
156
|
+
if s.stripDebugger {
|
|
157
|
+
start, end := statementRemovalRange(text, node)
|
|
158
|
+
edits = append(edits, textEdit{start: start, end: end})
|
|
159
|
+
}
|
|
160
|
+
case shimast.KindExpressionStatement:
|
|
161
|
+
expr := node.AsExpressionStatement().Expression
|
|
162
|
+
name, ok := callExpressionName(expr)
|
|
163
|
+
if ok && s.matchesCall(name) {
|
|
164
|
+
start, end := statementRemovalRange(text, node)
|
|
165
|
+
edits = append(edits, textEdit{start: start, end: end})
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
169
|
+
walk(child)
|
|
170
|
+
return false
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
for _, stmt := range file.Statements.Nodes {
|
|
174
|
+
walk(stmt)
|
|
175
|
+
}
|
|
176
|
+
return applyTextEdits(text, edits), nil
|
|
158
177
|
}
|
|
159
178
|
|
|
160
179
|
func (s *stripTransform) matchesCall(name string) bool {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
for _, pattern := range s.calls {
|
|
181
|
+
if pattern.matches(name) {
|
|
182
|
+
return true
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return false
|
|
167
186
|
}
|
|
168
187
|
|
|
169
188
|
func parseCallPattern(text string) (callPattern, error) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
parts := strings.Split(text, ".")
|
|
190
|
+
if len(parts) == 0 {
|
|
191
|
+
return callPattern{}, fmt.Errorf("empty call pattern")
|
|
192
|
+
}
|
|
193
|
+
for i, part := range parts {
|
|
194
|
+
if part == "" {
|
|
195
|
+
return callPattern{}, fmt.Errorf("invalid call pattern %q", text)
|
|
196
|
+
}
|
|
197
|
+
if part == "*" && i != len(parts)-1 {
|
|
198
|
+
// Only terminal wildcards have a clear statement-level meaning:
|
|
199
|
+
// logger.* matches logger.info(), logger.warn(), etc.
|
|
200
|
+
return callPattern{}, fmt.Errorf("wildcard is only supported at the end of call pattern %q", text)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
wildcard := parts[len(parts)-1] == "*"
|
|
204
|
+
if wildcard {
|
|
205
|
+
parts = parts[:len(parts)-1]
|
|
206
|
+
}
|
|
207
|
+
return callPattern{parts: parts, wildcard: wildcard}, nil
|
|
187
208
|
}
|
|
188
209
|
|
|
189
210
|
func (p callPattern) matches(name string) bool {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
211
|
+
parts := strings.Split(name, ".")
|
|
212
|
+
if p.wildcard {
|
|
213
|
+
if len(parts) <= len(p.parts) {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
return equalStringSlices(parts[:len(p.parts)], p.parts)
|
|
217
|
+
}
|
|
218
|
+
return equalStringSlices(parts, p.parts)
|
|
198
219
|
}
|
|
199
220
|
|
|
200
221
|
func callExpressionName(expr *shimast.Node) (string, bool) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
222
|
+
if expr == nil || expr.Kind != shimast.KindCallExpression {
|
|
223
|
+
return "", false
|
|
224
|
+
}
|
|
225
|
+
call := expr.AsCallExpression()
|
|
226
|
+
return dottedName(call.Expression)
|
|
206
227
|
}
|
|
207
228
|
|
|
208
229
|
func dottedName(expr *shimast.Node) (string, bool) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
if expr == nil {
|
|
231
|
+
return "", false
|
|
232
|
+
}
|
|
233
|
+
switch expr.Kind {
|
|
234
|
+
case shimast.KindIdentifier:
|
|
235
|
+
return expr.Text(), true
|
|
236
|
+
case shimast.KindPropertyAccessExpression:
|
|
237
|
+
prop := expr.AsPropertyAccessExpression()
|
|
238
|
+
left, ok := dottedName(prop.Expression)
|
|
239
|
+
if !ok || prop.Name() == nil {
|
|
240
|
+
return "", false
|
|
241
|
+
}
|
|
242
|
+
return left + "." + prop.Name().Text(), true
|
|
243
|
+
default:
|
|
244
|
+
return "", false
|
|
245
|
+
}
|
|
225
246
|
}
|
|
226
247
|
|
|
227
248
|
func parseJS(fileName string, text string) *shimast.SourceFile {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
249
|
+
normalized := filepath.ToSlash(fileName)
|
|
250
|
+
if !filepath.IsAbs(normalized) {
|
|
251
|
+
if abs, err := filepath.Abs(normalized); err == nil {
|
|
252
|
+
normalized = filepath.ToSlash(abs)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
opts := shimast.SourceFileParseOptions{FileName: normalized}
|
|
256
|
+
return shimparser.ParseSourceFile(opts, text, shimcore.ScriptKindJS)
|
|
236
257
|
}
|
|
237
258
|
|
|
238
259
|
func stringArrayConfig(config map[string]any, key string) ([]string, error) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
260
|
+
raw, ok := config[key]
|
|
261
|
+
if !ok || raw == nil {
|
|
262
|
+
return nil, nil
|
|
263
|
+
}
|
|
264
|
+
values, ok := raw.([]any)
|
|
265
|
+
if !ok {
|
|
266
|
+
return nil, fmt.Errorf("%q must be an array of strings", key)
|
|
267
|
+
}
|
|
268
|
+
out := make([]string, 0, len(values))
|
|
269
|
+
for i, value := range values {
|
|
270
|
+
text, ok := value.(string)
|
|
271
|
+
if !ok || strings.TrimSpace(text) == "" {
|
|
272
|
+
return nil, fmt.Errorf("%q[%d] must be a non-empty string", key, i)
|
|
273
|
+
}
|
|
274
|
+
out = append(out, text)
|
|
275
|
+
}
|
|
276
|
+
return out, nil
|
|
256
277
|
}
|
|
257
278
|
|
|
258
279
|
func statementRemovalRange(text string, node *shimast.Node) (int, int) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
280
|
+
// Remove the whole physical statement line when the statement starts after
|
|
281
|
+
// indentation. This keeps blank-line churn small for common one-statement
|
|
282
|
+
// debug calls.
|
|
283
|
+
start := clamp(node.Pos(), 0, len(text))
|
|
284
|
+
end := clamp(node.End(), start, len(text))
|
|
285
|
+
lineStart := start
|
|
286
|
+
for lineStart > 0 && text[lineStart-1] != '\n' && text[lineStart-1] != '\r' {
|
|
287
|
+
lineStart--
|
|
288
|
+
}
|
|
289
|
+
if strings.TrimSpace(text[lineStart:start]) == "" {
|
|
290
|
+
start = lineStart
|
|
291
|
+
}
|
|
292
|
+
if end < len(text) && text[end] == ';' {
|
|
293
|
+
end++
|
|
294
|
+
}
|
|
295
|
+
for end < len(text) && (text[end] == ' ' || text[end] == '\t') {
|
|
296
|
+
end++
|
|
297
|
+
}
|
|
298
|
+
if end < len(text) && text[end] == '\r' {
|
|
299
|
+
end++
|
|
300
|
+
}
|
|
301
|
+
if end < len(text) && text[end] == '\n' {
|
|
302
|
+
end++
|
|
303
|
+
}
|
|
304
|
+
return start, end
|
|
281
305
|
}
|
|
282
306
|
|
|
283
307
|
func applyTextEdits(text string, edits []textEdit) string {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
if len(edits) == 0 {
|
|
309
|
+
return text
|
|
310
|
+
}
|
|
311
|
+
sort.SliceStable(edits, func(i, j int) bool {
|
|
312
|
+
if edits[i].start == edits[j].start {
|
|
313
|
+
return edits[i].end > edits[j].end
|
|
314
|
+
}
|
|
315
|
+
return edits[i].start > edits[j].start
|
|
316
|
+
})
|
|
317
|
+
out := text
|
|
318
|
+
lastStart := len(text) + 1
|
|
319
|
+
for _, edit := range edits {
|
|
320
|
+
if edit.start < 0 || edit.end < edit.start || edit.start > len(out) {
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
if edit.end > lastStart {
|
|
324
|
+
// Nested edits are collapsed by letting the earlier source range own the
|
|
325
|
+
// bytes. This keeps repeated rule matches from corrupting offsets.
|
|
326
|
+
edit.end = lastStart
|
|
327
|
+
}
|
|
328
|
+
if edit.end > len(out) {
|
|
329
|
+
edit.end = len(out)
|
|
330
|
+
}
|
|
331
|
+
out = out[:edit.start] + edit.text + out[edit.end:]
|
|
332
|
+
lastStart = edit.start
|
|
333
|
+
}
|
|
334
|
+
return out
|
|
309
335
|
}
|
|
310
336
|
|
|
311
337
|
func findConfig(pluginsJSON string) (map[string]any, error) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
338
|
+
if strings.TrimSpace(pluginsJSON) == "" {
|
|
339
|
+
return nil, fmt.Errorf("@ttsc/strip: missing --plugins-json")
|
|
340
|
+
}
|
|
341
|
+
var entries []pluginEntry
|
|
342
|
+
if err := json.Unmarshal([]byte(pluginsJSON), &entries); err != nil {
|
|
343
|
+
return nil, fmt.Errorf("@ttsc/strip: invalid --plugins-json: %w", err)
|
|
344
|
+
}
|
|
345
|
+
for _, entry := range entries {
|
|
346
|
+
if entry.Name == "@ttsc/strip" {
|
|
347
|
+
if entry.Config == nil {
|
|
348
|
+
return map[string]any{}, nil
|
|
349
|
+
}
|
|
350
|
+
return entry.Config, nil
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return nil, fmt.Errorf("@ttsc/strip: plugin entry not found")
|
|
328
354
|
}
|
|
329
355
|
|
|
330
356
|
func isJavaScriptOutput(fileName string) bool {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
357
|
+
switch strings.ToLower(filepath.Ext(fileName)) {
|
|
358
|
+
case ".js", ".mjs", ".cjs":
|
|
359
|
+
return true
|
|
360
|
+
default:
|
|
361
|
+
return false
|
|
362
|
+
}
|
|
337
363
|
}
|
|
338
364
|
|
|
339
365
|
func equalStringSlices(a []string, b []string) bool {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
366
|
+
if len(a) != len(b) {
|
|
367
|
+
return false
|
|
368
|
+
}
|
|
369
|
+
for i := range a {
|
|
370
|
+
if a[i] != b[i] {
|
|
371
|
+
return false
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return true
|
|
349
375
|
}
|
|
350
376
|
|
|
351
377
|
func clamp(value int, min int, max int) int {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
378
|
+
if value < min {
|
|
379
|
+
return min
|
|
380
|
+
}
|
|
381
|
+
if value > max {
|
|
382
|
+
return max
|
|
383
|
+
}
|
|
384
|
+
return value
|
|
359
385
|
}
|