@ttsc/strip 0.7.2 → 0.8.0-dev.20260505

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 CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Guide Documents](https://img.shields.io/badge/Guide-Documents-forestgreen)](https://github.com/samchon/ttsc/tree/master/docs)
10
10
  [![Discord Badge](https://img.shields.io/badge/discord-samchon-d91965?style=flat&labelColor=5866f2&logo=discord&logoColor=white&link=https://discord.gg/E94XhzrUCZ)](https://discord.gg/E94XhzrUCZ)
11
11
 
12
- `@ttsc/strip` removes configured call-expression statements and debugger statements from emitted JavaScript.
12
+ `@ttsc/strip` removes configured call-expression statements and debugger statements from TypeScript source before emit.
13
13
 
14
14
  ## Setup
15
15
 
@@ -54,8 +54,8 @@ Call patterns match statement-level calls such as `console.log("debug")` or `ass
54
54
  // Keep lint first.
55
55
  { "transform": "@ttsc/lint", "config": { "no-var": "error" } },
56
56
 
57
- // Output plugins run after emit, in order.
58
- { "transform": "@ttsc/banner", "banner": "/*! @license MIT */" },
57
+ // First-party utilities use their documented source/emit hook order.
58
+ { "transform": "@ttsc/banner", "banner": "License MIT" },
59
59
  { "transform": "@ttsc/paths" },
60
60
  {
61
61
  "transform": "@ttsc/strip",
package/go.mod CHANGED
@@ -2,11 +2,7 @@ module github.com/samchon/ttsc/packages/strip
2
2
 
3
3
  go 1.26
4
4
 
5
- require (
6
- github.com/microsoft/typescript-go/shim/ast v0.0.0
7
- github.com/microsoft/typescript-go/shim/core v0.0.0
8
- github.com/microsoft/typescript-go/shim/parser v0.0.0
9
- )
5
+ require github.com/samchon/ttsc/packages/ttsc v0.0.0
10
6
 
11
7
  require (
12
8
  github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/strip",
3
- "version": "0.7.2",
3
+ "version": "0.8.0-dev.20260505",
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,36 @@
1
+ // Native sidecar entrypoint for `@ttsc/strip`.
1
2
  package main
2
3
 
3
4
  import (
4
- "fmt"
5
- "os"
5
+ "fmt"
6
+ "os"
7
+
8
+ "github.com/samchon/ttsc/packages/ttsc/utility"
6
9
  )
7
10
 
8
11
  const version = "0.0.1"
9
12
 
10
13
  func main() {
11
- os.Exit(run(os.Args[1:]))
14
+ os.Exit(run(os.Args[1:]))
12
15
  }
13
16
 
14
17
  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
- }
18
+ if len(args) == 0 {
19
+ fmt.Fprintln(os.Stderr, "@ttsc/strip: command required (expected build|transform|check|version)")
20
+ return 2
21
+ }
22
+ switch args[0] {
23
+ case "-v", "--version", "version":
24
+ fmt.Fprintf(os.Stdout, "@ttsc/strip %s\n", version)
25
+ return 0
26
+ case "build":
27
+ return utility.RunBuild(args[1:])
28
+ case "transform":
29
+ return utility.RunTransform(args[1:])
30
+ case "check":
31
+ return 0
32
+ default:
33
+ fmt.Fprintf(os.Stderr, "@ttsc/strip: unknown command %q\n", args[0])
34
+ return 2
35
+ }
31
36
  }
package/plugin/strip.go CHANGED
@@ -1,359 +1,3 @@
1
1
  package main
2
2
 
3
- import (
4
- "encoding/json"
5
- "flag"
6
- "fmt"
7
- "os"
8
- "path/filepath"
9
- "sort"
10
- "strings"
11
-
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"
15
- )
16
-
17
- type pluginEntry struct {
18
- Config map[string]any `json:"config"`
19
- Name string `json:"name"`
20
- Stage string `json:"stage"`
21
- }
22
-
23
- type stripTransform struct {
24
- calls []callPattern
25
- stripDebugger bool
26
- }
27
-
28
- type callPattern struct {
29
- parts []string
30
- wildcard bool
31
- }
32
-
33
- type textEdit struct {
34
- start int
35
- end int
36
- text string
37
- }
38
-
39
- 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
83
- }
84
-
85
- 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)
91
- }
92
-
93
- 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
119
- }
120
-
121
- 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
158
- }
159
-
160
- 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
167
- }
168
-
169
- 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
187
- }
188
-
189
- 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)
198
- }
199
-
200
- 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)
206
- }
207
-
208
- 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
- }
225
- }
226
-
227
- 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)
236
- }
237
-
238
- 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
256
- }
257
-
258
- 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
281
- }
282
-
283
- 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
309
- }
310
-
311
- 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")
328
- }
329
-
330
- 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
- }
337
- }
338
-
339
- 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
349
- }
350
-
351
- 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
359
- }
3
+ // Strip transform logic lives in github.com/samchon/ttsc/packages/ttsc/utility.
package/src/index.cjs CHANGED
@@ -7,6 +7,9 @@ module.exports = function createTtscStrip() {
7
7
  return {
8
8
  name: "@ttsc/strip",
9
9
  source: path.resolve(__dirname, "..", "plugin"),
10
- stage: "output",
10
+ stage: "transform",
11
+ hooks: {
12
+ source: true,
13
+ },
11
14
  };
12
15
  };