@ttsc/strip 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@ttsc/strip`
2
2
 
3
- ![banner of @ttsc/strip](https://raw.githubusercontent.com/samchon/ttsc/refs/heads/master/assets/og.jpg)
3
+ ![banner of @ttsc/strip](https://ttsc.dev/og.jpg)
4
4
 
5
5
  [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/samchon/ttsc/blob/master/LICENSE)
6
6
  [![NPM Version](https://img.shields.io/npm/v/@ttsc/strip.svg)](https://www.npmjs.com/package/@ttsc/strip)
@@ -0,0 +1,268 @@
1
+ package strip
2
+
3
+ import (
4
+ "fmt"
5
+ "strings"
6
+
7
+ shimast "github.com/microsoft/typescript-go/shim/ast"
8
+
9
+ "github.com/samchon/ttsc/packages/ttsc/driver"
10
+ )
11
+
12
+ func init() {
13
+ driver.RegisterPlugin(plugin{})
14
+ }
15
+
16
+ type plugin struct{}
17
+
18
+ func (plugin) ApplyProgram(prog *driver.Program, ctx driver.PluginContext) error {
19
+ rewriter, err := parseStrip(ctx.Entry.Config)
20
+ if err != nil {
21
+ return err
22
+ }
23
+ for _, file := range prog.SourceFiles() {
24
+ rewriter.apply(file)
25
+ }
26
+ return nil
27
+ }
28
+
29
+ type stripRewriter struct {
30
+ calls []callPattern
31
+ stripDebugger bool
32
+ }
33
+
34
+ type callPattern struct {
35
+ parts []string
36
+ wildcard bool
37
+ }
38
+
39
+ func parseStrip(config map[string]any) (*stripRewriter, error) {
40
+ _, hasCalls := config["calls"]
41
+ _, hasStatements := config["statements"]
42
+ if !hasCalls && !hasStatements {
43
+ config = map[string]any{
44
+ "calls": []any{"console.log", "console.debug", "assert.*"},
45
+ "statements": []any{"debugger"},
46
+ }
47
+ }
48
+ calls, err := stringArrayConfig(config, "calls")
49
+ if err != nil {
50
+ return nil, fmt.Errorf("@ttsc/strip: %w", err)
51
+ }
52
+ statements, err := stringArrayConfig(config, "statements")
53
+ if err != nil {
54
+ return nil, fmt.Errorf("@ttsc/strip: %w", err)
55
+ }
56
+ out := &stripRewriter{}
57
+ for _, call := range calls {
58
+ pattern, err := parseCallPattern(call)
59
+ if err != nil {
60
+ return nil, fmt.Errorf("@ttsc/strip: %w", err)
61
+ }
62
+ out.calls = append(out.calls, pattern)
63
+ }
64
+ for _, statement := range statements {
65
+ switch statement {
66
+ case "debugger":
67
+ out.stripDebugger = true
68
+ default:
69
+ return nil, fmt.Errorf("@ttsc/strip: unsupported statement pattern %q", statement)
70
+ }
71
+ }
72
+ return out, nil
73
+ }
74
+
75
+ func (s *stripRewriter) apply(file *shimast.SourceFile) {
76
+ if s == nil || file == nil || (len(s.calls) == 0 && !s.stripDebugger) {
77
+ return
78
+ }
79
+ filterStatements(file.Statements, s)
80
+ }
81
+
82
+ func filterStatements(list *shimast.NodeList, strip *stripRewriter) {
83
+ if list == nil || len(list.Nodes) == 0 {
84
+ return
85
+ }
86
+ out := make([]*shimast.Node, 0, len(list.Nodes))
87
+ for _, stmt := range list.Nodes {
88
+ if shouldStripStatement(stmt, strip) {
89
+ continue
90
+ }
91
+ filterChildStatements(stmt, strip)
92
+ out = append(out, stmt)
93
+ }
94
+ list.Nodes = out
95
+ }
96
+
97
+ func filterChildStatements(node *shimast.Node, strip *stripRewriter) {
98
+ if node == nil {
99
+ return
100
+ }
101
+ filterEmbeddedStatements(node, strip)
102
+ if node.CanHaveStatements() {
103
+ filterStatements(node.StatementList(), strip)
104
+ }
105
+ node.ForEachChild(func(child *shimast.Node) bool {
106
+ filterChildStatements(child, strip)
107
+ return false
108
+ })
109
+ }
110
+
111
+ func filterEmbeddedStatements(node *shimast.Node, strip *stripRewriter) {
112
+ switch node.Kind {
113
+ case shimast.KindIfStatement:
114
+ stmt := node.AsIfStatement()
115
+ stmt.ThenStatement = filterEmbeddedStatement(stmt.ThenStatement, strip)
116
+ stmt.ElseStatement = filterEmbeddedStatement(stmt.ElseStatement, strip)
117
+ case shimast.KindDoStatement:
118
+ stmt := node.AsDoStatement()
119
+ stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
120
+ case shimast.KindWhileStatement:
121
+ stmt := node.AsWhileStatement()
122
+ stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
123
+ case shimast.KindForStatement:
124
+ stmt := node.AsForStatement()
125
+ stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
126
+ case shimast.KindForInStatement, shimast.KindForOfStatement:
127
+ stmt := node.AsForInOrOfStatement()
128
+ stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
129
+ case shimast.KindWithStatement:
130
+ stmt := node.AsWithStatement()
131
+ stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
132
+ case shimast.KindLabeledStatement:
133
+ stmt := node.AsLabeledStatement()
134
+ stmt.Statement = filterEmbeddedStatement(stmt.Statement, strip)
135
+ }
136
+ }
137
+
138
+ func filterEmbeddedStatement(stmt *shimast.Statement, strip *stripRewriter) *shimast.Statement {
139
+ if stmt == nil {
140
+ return nil
141
+ }
142
+ if shouldStripStatement(stmt, strip) {
143
+ return emptyStatement(stmt)
144
+ }
145
+ filterChildStatements(stmt, strip)
146
+ return stmt
147
+ }
148
+
149
+ func emptyStatement(original *shimast.Node) *shimast.Statement {
150
+ empty := shimast.NewNodeFactory(shimast.NodeFactoryHooks{}).NewEmptyStatement()
151
+ empty.Flags |= shimast.NodeFlagsSynthesized
152
+ if original != nil {
153
+ empty.Loc = original.Loc
154
+ }
155
+ return empty
156
+ }
157
+
158
+ func shouldStripStatement(node *shimast.Node, strip *stripRewriter) bool {
159
+ if node == nil {
160
+ return false
161
+ }
162
+ switch node.Kind {
163
+ case shimast.KindDebuggerStatement:
164
+ return strip.stripDebugger
165
+ case shimast.KindExpressionStatement:
166
+ expr := node.AsExpressionStatement().Expression
167
+ name, ok := callExpressionName(expr)
168
+ return ok && strip.matchesCall(name)
169
+ default:
170
+ return false
171
+ }
172
+ }
173
+
174
+ func (s *stripRewriter) matchesCall(name string) bool {
175
+ for _, pattern := range s.calls {
176
+ if pattern.matches(name) {
177
+ return true
178
+ }
179
+ }
180
+ return false
181
+ }
182
+
183
+ func parseCallPattern(text string) (callPattern, error) {
184
+ parts := strings.Split(text, ".")
185
+ for i, part := range parts {
186
+ if part == "" {
187
+ return callPattern{}, fmt.Errorf("invalid call pattern %q", text)
188
+ }
189
+ if part == "*" && i != len(parts)-1 {
190
+ return callPattern{}, fmt.Errorf("wildcard is only supported at the end of call pattern %q", text)
191
+ }
192
+ }
193
+ wildcard := parts[len(parts)-1] == "*"
194
+ if wildcard {
195
+ parts = parts[:len(parts)-1]
196
+ }
197
+ return callPattern{parts: parts, wildcard: wildcard}, nil
198
+ }
199
+
200
+ func (p callPattern) matches(name string) bool {
201
+ parts := strings.Split(name, ".")
202
+ if p.wildcard {
203
+ if len(parts) <= len(p.parts) {
204
+ return false
205
+ }
206
+ return equalStringSlices(parts[:len(p.parts)], p.parts)
207
+ }
208
+ return equalStringSlices(parts, p.parts)
209
+ }
210
+
211
+ func callExpressionName(expr *shimast.Node) (string, bool) {
212
+ if expr == nil || expr.Kind != shimast.KindCallExpression {
213
+ return "", false
214
+ }
215
+ call := expr.AsCallExpression()
216
+ return dottedName(call.Expression)
217
+ }
218
+
219
+ func dottedName(expr *shimast.Node) (string, bool) {
220
+ if expr == nil {
221
+ return "", false
222
+ }
223
+ switch expr.Kind {
224
+ case shimast.KindIdentifier:
225
+ return expr.Text(), true
226
+ case shimast.KindPropertyAccessExpression:
227
+ prop := expr.AsPropertyAccessExpression()
228
+ left, ok := dottedName(prop.Expression)
229
+ if !ok || prop.Name() == nil {
230
+ return "", false
231
+ }
232
+ return left + "." + prop.Name().Text(), true
233
+ default:
234
+ return "", false
235
+ }
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 equalStringSlices(left, right []string) bool {
259
+ if len(left) != len(right) {
260
+ return false
261
+ }
262
+ for i := range left {
263
+ if left[i] != right[i] {
264
+ return false
265
+ }
266
+ }
267
+ return true
268
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/strip",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
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": {
@@ -21,6 +21,7 @@
21
21
  },
22
22
  "files": [
23
23
  "README.md",
24
+ "driver/strip.go",
24
25
  "src/index.cjs",
25
26
  "go.mod",
26
27
  "plugin/main.go",
package/plugin/strip.go CHANGED
@@ -1,3 +1,3 @@
1
1
  package main
2
2
 
3
- // Strip transform logic lives in github.com/samchon/ttsc/packages/ttsc/utility.
3
+ import _ "github.com/samchon/ttsc/packages/strip/driver"
package/src/index.cjs CHANGED
@@ -6,7 +6,7 @@ const path = require("node:path");
6
6
  module.exports = function createTtscStrip() {
7
7
  return {
8
8
  name: "@ttsc/strip",
9
- source: path.resolve(__dirname, "..", "plugin"),
9
+ source: path.resolve(__dirname, "..", "driver"),
10
10
  stage: "transform",
11
11
  };
12
12
  };