@ttsc/strip 0.7.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/strip",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "First-party ttsc plugin that removes configured calls and statements from emitted JavaScript.",
5
5
  "main": "src/index.cjs",
6
6
  "exports": {
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
- "fmt"
5
- "os"
9
+ "fmt"
10
+ "os"
6
11
  )
7
12
 
8
13
  const version = "0.0.1"
9
14
 
10
15
  func main() {
11
- os.Exit(run(os.Args[1:]))
16
+ os.Exit(run(os.Args[1:]))
12
17
  }
13
18
 
14
19
  func run(args []string) int {
15
- if len(args) == 0 {
16
- fmt.Fprintln(os.Stderr, "@ttsc/strip: command required (expected output|version)")
17
- return 2
18
- }
19
- switch args[0] {
20
- case "-v", "--version", "version":
21
- fmt.Fprintf(os.Stdout, "@ttsc/strip %s\n", version)
22
- return 0
23
- case "check":
24
- return 0
25
- case "output":
26
- return RunOutput(args[1:])
27
- default:
28
- fmt.Fprintf(os.Stderr, "@ttsc/strip: unknown command %q\n", args[0])
29
- return 2
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
- "encoding/json"
5
- "flag"
6
- "fmt"
7
- "os"
8
- "path/filepath"
9
- "sort"
10
- "strings"
10
+ "encoding/json"
11
+ "flag"
12
+ "fmt"
13
+ "os"
14
+ "path/filepath"
15
+ "sort"
16
+ "strings"
11
17
 
12
- shimast "github.com/microsoft/typescript-go/shim/ast"
13
- shimcore "github.com/microsoft/typescript-go/shim/core"
14
- shimparser "github.com/microsoft/typescript-go/shim/parser"
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
- Config map[string]any `json:"config"`
19
- Name string `json:"name"`
20
- Stage string `json:"stage"`
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
- calls []callPattern
25
- stripDebugger bool
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
- parts []string
30
- wildcard bool
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
- start int
35
- end int
36
- text string
46
+ start int
47
+ end int
48
+ text string
37
49
  }
38
50
 
39
51
  func RunOutput(args []string) int {
40
- fs := flag.NewFlagSet("output", flag.ContinueOnError)
41
- fs.SetOutput(os.Stderr)
42
- file := fs.String("file", "", "emitted file to transform")
43
- out := fs.String("out", "", "write transformed text to this file instead of updating --file")
44
- _ = fs.String("cwd", "", "project directory")
45
- _ = fs.String("outDir", "", "emit directory override")
46
- pluginsJSON := fs.String("plugins-json", "", "ttsc plugin manifest JSON")
47
- _ = fs.String("tsconfig", "tsconfig.json", "project tsconfig")
48
- if err := fs.Parse(args); err != nil {
49
- return 2
50
- }
51
- if *file == "" {
52
- fmt.Fprintln(os.Stderr, "@ttsc/strip: output requires --file")
53
- return 2
54
- }
55
- config, err := findConfig(*pluginsJSON)
56
- if err != nil {
57
- fmt.Fprintln(os.Stderr, err)
58
- return 2
59
- }
60
- text, err := os.ReadFile(*file)
61
- if err != nil {
62
- fmt.Fprintf(os.Stderr, "@ttsc/strip: read %s: %v\n", *file, err)
63
- return 2
64
- }
65
- patched, err := Apply(*file, string(text), config)
66
- if err != nil {
67
- fmt.Fprintln(os.Stderr, err)
68
- return 2
69
- }
70
- target := *file
71
- if *out != "" {
72
- target = *out
73
- }
74
- if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
75
- fmt.Fprintf(os.Stderr, "@ttsc/strip: mkdir: %v\n", err)
76
- return 2
77
- }
78
- if err := os.WriteFile(target, []byte(patched), 0o644); err != nil {
79
- fmt.Fprintf(os.Stderr, "@ttsc/strip: write %s: %v\n", target, err)
80
- return 2
81
- }
82
- return 0
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
- strip, err := parseStrip(config)
87
- if err != nil {
88
- return "", err
89
- }
90
- return strip.apply(fileName, text)
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
- calls, err := stringArrayConfig(config, "calls")
95
- if err != nil {
96
- return nil, fmt.Errorf("@ttsc/strip: %w", err)
97
- }
98
- statements, err := stringArrayConfig(config, "statements")
99
- if err != nil {
100
- return nil, fmt.Errorf("@ttsc/strip: %w", err)
101
- }
102
- out := &stripTransform{}
103
- for _, call := range calls {
104
- pattern, err := parseCallPattern(call)
105
- if err != nil {
106
- return nil, fmt.Errorf("@ttsc/strip: %w", err)
107
- }
108
- out.calls = append(out.calls, pattern)
109
- }
110
- for _, statement := range statements {
111
- switch statement {
112
- case "debugger":
113
- out.stripDebugger = true
114
- default:
115
- return nil, fmt.Errorf("@ttsc/strip: unsupported statement pattern %q", statement)
116
- }
117
- }
118
- return out, nil
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
- if s == nil || !isJavaScriptOutput(fileName) || (len(s.calls) == 0 && !s.stripDebugger) {
123
- return text, nil
124
- }
125
- file := parseJS(fileName, text)
126
- if file == nil {
127
- return text, nil
128
- }
129
- edits := make([]textEdit, 0)
130
- var walk func(*shimast.Node)
131
- walk = func(node *shimast.Node) {
132
- if node == nil {
133
- return
134
- }
135
- switch node.Kind {
136
- case shimast.KindDebuggerStatement:
137
- if s.stripDebugger {
138
- start, end := statementRemovalRange(text, node)
139
- edits = append(edits, textEdit{start: start, end: end})
140
- }
141
- case shimast.KindExpressionStatement:
142
- expr := node.AsExpressionStatement().Expression
143
- name, ok := callExpressionName(expr)
144
- if ok && s.matchesCall(name) {
145
- start, end := statementRemovalRange(text, node)
146
- edits = append(edits, textEdit{start: start, end: end})
147
- }
148
- }
149
- node.ForEachChild(func(child *shimast.Node) bool {
150
- walk(child)
151
- return false
152
- })
153
- }
154
- for _, stmt := range file.Statements.Nodes {
155
- walk(stmt)
156
- }
157
- return applyTextEdits(text, edits), nil
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
- for _, pattern := range s.calls {
162
- if pattern.matches(name) {
163
- return true
164
- }
165
- }
166
- return false
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
- parts := strings.Split(text, ".")
171
- if len(parts) == 0 {
172
- return callPattern{}, fmt.Errorf("empty call pattern")
173
- }
174
- for i, part := range parts {
175
- if part == "" {
176
- return callPattern{}, fmt.Errorf("invalid call pattern %q", text)
177
- }
178
- if part == "*" && i != len(parts)-1 {
179
- return callPattern{}, fmt.Errorf("wildcard is only supported at the end of call pattern %q", text)
180
- }
181
- }
182
- wildcard := parts[len(parts)-1] == "*"
183
- if wildcard {
184
- parts = parts[:len(parts)-1]
185
- }
186
- return callPattern{parts: parts, wildcard: wildcard}, nil
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
- parts := strings.Split(name, ".")
191
- if p.wildcard {
192
- if len(parts) <= len(p.parts) {
193
- return false
194
- }
195
- return equalStringSlices(parts[:len(p.parts)], p.parts)
196
- }
197
- return equalStringSlices(parts, p.parts)
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
- if expr == nil || expr.Kind != shimast.KindCallExpression {
202
- return "", false
203
- }
204
- call := expr.AsCallExpression()
205
- return dottedName(call.Expression)
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
- if expr == nil {
210
- return "", false
211
- }
212
- switch expr.Kind {
213
- case shimast.KindIdentifier:
214
- return expr.Text(), true
215
- case shimast.KindPropertyAccessExpression:
216
- prop := expr.AsPropertyAccessExpression()
217
- left, ok := dottedName(prop.Expression)
218
- if !ok || prop.Name() == nil {
219
- return "", false
220
- }
221
- return left + "." + prop.Name().Text(), true
222
- default:
223
- return "", false
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
- normalized := filepath.ToSlash(fileName)
229
- if !filepath.IsAbs(normalized) {
230
- if abs, err := filepath.Abs(normalized); err == nil {
231
- normalized = filepath.ToSlash(abs)
232
- }
233
- }
234
- opts := shimast.SourceFileParseOptions{FileName: normalized}
235
- return shimparser.ParseSourceFile(opts, text, shimcore.ScriptKindJS)
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
- raw, ok := config[key]
240
- if !ok || raw == nil {
241
- return nil, nil
242
- }
243
- values, ok := raw.([]any)
244
- if !ok {
245
- return nil, fmt.Errorf("%q must be an array of strings", key)
246
- }
247
- out := make([]string, 0, len(values))
248
- for i, value := range values {
249
- text, ok := value.(string)
250
- if !ok || strings.TrimSpace(text) == "" {
251
- return nil, fmt.Errorf("%q[%d] must be a non-empty string", key, i)
252
- }
253
- out = append(out, text)
254
- }
255
- return out, nil
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
- start := clamp(node.Pos(), 0, len(text))
260
- end := clamp(node.End(), start, len(text))
261
- lineStart := start
262
- for lineStart > 0 && text[lineStart-1] != '\n' && text[lineStart-1] != '\r' {
263
- lineStart--
264
- }
265
- if strings.TrimSpace(text[lineStart:start]) == "" {
266
- start = lineStart
267
- }
268
- if end < len(text) && text[end] == ';' {
269
- end++
270
- }
271
- for end < len(text) && (text[end] == ' ' || text[end] == '\t') {
272
- end++
273
- }
274
- if end < len(text) && text[end] == '\r' {
275
- end++
276
- }
277
- if end < len(text) && text[end] == '\n' {
278
- end++
279
- }
280
- return start, end
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
- if len(edits) == 0 {
285
- return text
286
- }
287
- sort.SliceStable(edits, func(i, j int) bool {
288
- if edits[i].start == edits[j].start {
289
- return edits[i].end > edits[j].end
290
- }
291
- return edits[i].start > edits[j].start
292
- })
293
- out := text
294
- lastStart := len(text) + 1
295
- for _, edit := range edits {
296
- if edit.start < 0 || edit.end < edit.start || edit.start > len(out) {
297
- continue
298
- }
299
- if edit.end > lastStart {
300
- edit.end = lastStart
301
- }
302
- if edit.end > len(out) {
303
- edit.end = len(out)
304
- }
305
- out = out[:edit.start] + edit.text + out[edit.end:]
306
- lastStart = edit.start
307
- }
308
- return out
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
- if strings.TrimSpace(pluginsJSON) == "" {
313
- return nil, fmt.Errorf("@ttsc/strip: missing --plugins-json")
314
- }
315
- var entries []pluginEntry
316
- if err := json.Unmarshal([]byte(pluginsJSON), &entries); err != nil {
317
- return nil, fmt.Errorf("@ttsc/strip: invalid --plugins-json: %w", err)
318
- }
319
- for _, entry := range entries {
320
- if entry.Name == "@ttsc/strip" {
321
- if entry.Config == nil {
322
- return map[string]any{}, nil
323
- }
324
- return entry.Config, nil
325
- }
326
- }
327
- return nil, fmt.Errorf("@ttsc/strip: plugin entry not found")
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
- switch strings.ToLower(filepath.Ext(fileName)) {
332
- case ".js", ".mjs", ".cjs":
333
- return true
334
- default:
335
- return false
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
- if len(a) != len(b) {
341
- return false
342
- }
343
- for i := range a {
344
- if a[i] != b[i] {
345
- return false
346
- }
347
- }
348
- return true
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
- if value < min {
353
- return min
354
- }
355
- if value > max {
356
- return max
357
- }
358
- return value
378
+ if value < min {
379
+ return min
380
+ }
381
+ if value > max {
382
+ return max
383
+ }
384
+ return value
359
385
  }