@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 +1 -1
- package/driver/strip.go +268 -0
- package/package.json +2 -1
- package/plugin/strip.go +1 -1
- package/src/index.cjs +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@ttsc/strip`
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
[](https://github.com/samchon/ttsc/blob/master/LICENSE)
|
|
6
6
|
[](https://www.npmjs.com/package/@ttsc/strip)
|
package/driver/strip.go
ADDED
|
@@ -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.
|
|
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
package/src/index.cjs
CHANGED