@dacely/toildefender 0.1.0 → 0.1.2
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/LICENSE +661 -661
- package/NOTICE.md +16 -16
- package/README.md +467 -380
- package/cli.js +168 -168
- package/defendjs.js +6 -6
- package/docs/all-modes-output.demo.js +673 -673
- package/estest.js +49 -49
- package/esutils.js +107 -107
- package/logger.js +28 -28
- package/obfuscator.js +534 -534
- package/package.json +108 -108
- package/processors/deadCode.js +62 -62
- package/processors/flattener.js +808 -808
- package/processors/health.js +55 -55
- package/processors/identifiers.js +256 -256
- package/processors/literalObfuscator.js +40 -40
- package/processors/literals.js +233 -233
- package/processors/methods.js +332 -332
- package/processors/modules.js +231 -231
- package/processors/normalizer.js +490 -490
- package/processors/numericVm.js +950 -950
- package/processors/postprocessing.js +69 -69
- package/processors/preprocessing.js +248 -248
- package/processors/scopes.js +177 -177
- package/processors/uglifier.js +25 -25
- package/processors/variables.js +185 -185
- package/toildefender.js +7 -7
- package/traverser.js +115 -115
- package/utils.js +135 -135
package/processors/variables.js
CHANGED
|
@@ -1,185 +1,185 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var assert = require("assert");
|
|
4
|
-
|
|
5
|
-
var _ = require("lodash");
|
|
6
|
-
|
|
7
|
-
var estest = require("../estest");
|
|
8
|
-
var ESUtils = require("../esutils");
|
|
9
|
-
var traverser = require("../traverser");
|
|
10
|
-
var utils = require("../utils");
|
|
11
|
-
|
|
12
|
-
module.exports = class Variables {
|
|
13
|
-
|
|
14
|
-
constructor (logger) {
|
|
15
|
-
this.logger = logger;
|
|
16
|
-
this.esutils = new ESUtils(logger);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Removes the id property from FunctionExpressions.
|
|
21
|
-
* They trip up functionDeclarationToExpression and escope (?),
|
|
22
|
-
* causing scopes.js to incorrectly rename some references.
|
|
23
|
-
* @param {Node} ast Root node
|
|
24
|
-
* @returns {Node} Root node
|
|
25
|
-
*/
|
|
26
|
-
removeFunctionExpressionIds (ast) {
|
|
27
|
-
return traverser.traverse(ast, [], (node, stack) => {
|
|
28
|
-
if (node.type == "FunctionExpression" && node.id) {
|
|
29
|
-
node.id = null;
|
|
30
|
-
}
|
|
31
|
-
return node;
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Converts function declarations like
|
|
37
|
-
* function test() { ... }
|
|
38
|
-
* to function expression variables like
|
|
39
|
-
* var test = function() { ... };
|
|
40
|
-
* @param {Node} ast Root node
|
|
41
|
-
* @param {ScopeManager} scopeManager Scope manager
|
|
42
|
-
*/
|
|
43
|
-
functionDeclarationToExpression (ast, scopeManager) {
|
|
44
|
-
assert.ok(estest.isNode(ast));
|
|
45
|
-
|
|
46
|
-
this.esutils.setParentsRecursive(ast);
|
|
47
|
-
|
|
48
|
-
scopeManager.scopes.forEach(scope => {
|
|
49
|
-
scope.variables.forEach(variable => {
|
|
50
|
-
variable.defs.forEach(def => {
|
|
51
|
-
if (def.type == "FunctionName") {
|
|
52
|
-
assert(estest.isFunction(def.node));
|
|
53
|
-
/**
|
|
54
|
-
* Here you have to ensure that def.node is statement.
|
|
55
|
-
* Expressions like { foo: function() { ... }} are parsed
|
|
56
|
-
* as a FunctionExpression with an id, which are then
|
|
57
|
-
* mistakingly replaced with EmptyStatements.
|
|
58
|
-
*/
|
|
59
|
-
if (estest.isStatement(def.node)) {
|
|
60
|
-
this.esutils.replaceNode(ast, def.node, { type: "EmptyStatement" });
|
|
61
|
-
this.esutils.insertIntoScope(scope, {
|
|
62
|
-
type: "VariableDeclaration",
|
|
63
|
-
kind: "var",
|
|
64
|
-
declarations: [
|
|
65
|
-
{
|
|
66
|
-
type: "VariableDeclarator",
|
|
67
|
-
id: def.node.id,
|
|
68
|
-
init: {
|
|
69
|
-
type: "FunctionExpression",
|
|
70
|
-
params: def.node.params,
|
|
71
|
-
body: def.node.body
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
]
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Renames identifiers with unique names, e.g.
|
|
85
|
-
* var a, b = 5;
|
|
86
|
-
* to
|
|
87
|
-
* var $$var$123$a, $$var$123$b = 5;
|
|
88
|
-
* @param {Node} ast Root node
|
|
89
|
-
* @param {ScopeManager} scopeManager Scope manager
|
|
90
|
-
*/
|
|
91
|
-
obfuscateIdentifiers (ast, scopeManager) {
|
|
92
|
-
scopeManager.scopes.forEach(scope => {
|
|
93
|
-
if (scope.isStatic()) {
|
|
94
|
-
scope.variables.sort((a, b) => {
|
|
95
|
-
if (a.tainted) {
|
|
96
|
-
return 1;
|
|
97
|
-
}
|
|
98
|
-
if (b.tainted) {
|
|
99
|
-
return -1;
|
|
100
|
-
}
|
|
101
|
-
return (b.identifiers.length + b.references.length) - (a.identifiers.length + a.references.length);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
for (let variable of scope.variables) {
|
|
105
|
-
var name = "$$var$" + utils.hash(variable) + "$" + variable.name;
|
|
106
|
-
|
|
107
|
-
if (variable.tainted) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (variable.identifiers.length === 0) {
|
|
112
|
-
// do not change names since this is a special name
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
for (let def of variable.identifiers) {
|
|
117
|
-
// change definition's name
|
|
118
|
-
def.name = name;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
for (let ref of variable.references) {
|
|
122
|
-
// change reference's name
|
|
123
|
-
ref.identifier.name = name;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Replaces direct parameter references like
|
|
132
|
-
* function (a) {
|
|
133
|
-
* return a;
|
|
134
|
-
* }
|
|
135
|
-
* to copys like
|
|
136
|
-
* function (a) {
|
|
137
|
-
* var $$arg$abc = a;
|
|
138
|
-
* return $$arg$abc;
|
|
139
|
-
* }
|
|
140
|
-
* @param {Node} ast Root node
|
|
141
|
-
* @param {ScopeManager} scopeManager Scope manager
|
|
142
|
-
*/
|
|
143
|
-
redefineParameters (ast, scopeManager) {
|
|
144
|
-
function getArgumentIndex(method, identifier) {
|
|
145
|
-
assert(method.type == "FunctionDeclaration" || method.type == "FunctionExpression");
|
|
146
|
-
assert(identifier.type == "Identifier");
|
|
147
|
-
for (var i = 0; i < method.params.length; ++i) {
|
|
148
|
-
if (method.params[i].name == identifier.name) {
|
|
149
|
-
return i;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return -1;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
var rng = new utils.UniqueRandomAlpha(3);
|
|
156
|
-
|
|
157
|
-
scopeManager.scopes.forEach(scope => {
|
|
158
|
-
scope.variables.forEach(variable => {
|
|
159
|
-
variable.defs.forEach(def => {
|
|
160
|
-
if (def.type == "Parameter") {
|
|
161
|
-
assert(def.name.type == "Identifier");
|
|
162
|
-
var name = "$$arg$" + rng.get();
|
|
163
|
-
|
|
164
|
-
this.esutils.insertIntoScope(scope, {
|
|
165
|
-
type: "VariableDeclaration",
|
|
166
|
-
kind: "var",
|
|
167
|
-
declarations: [
|
|
168
|
-
{
|
|
169
|
-
type: "VariableDeclarator",
|
|
170
|
-
id: { type: "Identifier", name: name },
|
|
171
|
-
init: def.name
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
variable.references.forEach(reference => {
|
|
177
|
-
reference.identifier.name = name;
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
|
|
5
|
+
var _ = require("lodash");
|
|
6
|
+
|
|
7
|
+
var estest = require("../estest");
|
|
8
|
+
var ESUtils = require("../esutils");
|
|
9
|
+
var traverser = require("../traverser");
|
|
10
|
+
var utils = require("../utils");
|
|
11
|
+
|
|
12
|
+
module.exports = class Variables {
|
|
13
|
+
|
|
14
|
+
constructor (logger) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.esutils = new ESUtils(logger);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Removes the id property from FunctionExpressions.
|
|
21
|
+
* They trip up functionDeclarationToExpression and escope (?),
|
|
22
|
+
* causing scopes.js to incorrectly rename some references.
|
|
23
|
+
* @param {Node} ast Root node
|
|
24
|
+
* @returns {Node} Root node
|
|
25
|
+
*/
|
|
26
|
+
removeFunctionExpressionIds (ast) {
|
|
27
|
+
return traverser.traverse(ast, [], (node, stack) => {
|
|
28
|
+
if (node.type == "FunctionExpression" && node.id) {
|
|
29
|
+
node.id = null;
|
|
30
|
+
}
|
|
31
|
+
return node;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Converts function declarations like
|
|
37
|
+
* function test() { ... }
|
|
38
|
+
* to function expression variables like
|
|
39
|
+
* var test = function() { ... };
|
|
40
|
+
* @param {Node} ast Root node
|
|
41
|
+
* @param {ScopeManager} scopeManager Scope manager
|
|
42
|
+
*/
|
|
43
|
+
functionDeclarationToExpression (ast, scopeManager) {
|
|
44
|
+
assert.ok(estest.isNode(ast));
|
|
45
|
+
|
|
46
|
+
this.esutils.setParentsRecursive(ast);
|
|
47
|
+
|
|
48
|
+
scopeManager.scopes.forEach(scope => {
|
|
49
|
+
scope.variables.forEach(variable => {
|
|
50
|
+
variable.defs.forEach(def => {
|
|
51
|
+
if (def.type == "FunctionName") {
|
|
52
|
+
assert(estest.isFunction(def.node));
|
|
53
|
+
/**
|
|
54
|
+
* Here you have to ensure that def.node is statement.
|
|
55
|
+
* Expressions like { foo: function() { ... }} are parsed
|
|
56
|
+
* as a FunctionExpression with an id, which are then
|
|
57
|
+
* mistakingly replaced with EmptyStatements.
|
|
58
|
+
*/
|
|
59
|
+
if (estest.isStatement(def.node)) {
|
|
60
|
+
this.esutils.replaceNode(ast, def.node, { type: "EmptyStatement" });
|
|
61
|
+
this.esutils.insertIntoScope(scope, {
|
|
62
|
+
type: "VariableDeclaration",
|
|
63
|
+
kind: "var",
|
|
64
|
+
declarations: [
|
|
65
|
+
{
|
|
66
|
+
type: "VariableDeclarator",
|
|
67
|
+
id: def.node.id,
|
|
68
|
+
init: {
|
|
69
|
+
type: "FunctionExpression",
|
|
70
|
+
params: def.node.params,
|
|
71
|
+
body: def.node.body
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Renames identifiers with unique names, e.g.
|
|
85
|
+
* var a, b = 5;
|
|
86
|
+
* to
|
|
87
|
+
* var $$var$123$a, $$var$123$b = 5;
|
|
88
|
+
* @param {Node} ast Root node
|
|
89
|
+
* @param {ScopeManager} scopeManager Scope manager
|
|
90
|
+
*/
|
|
91
|
+
obfuscateIdentifiers (ast, scopeManager) {
|
|
92
|
+
scopeManager.scopes.forEach(scope => {
|
|
93
|
+
if (scope.isStatic()) {
|
|
94
|
+
scope.variables.sort((a, b) => {
|
|
95
|
+
if (a.tainted) {
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
if (b.tainted) {
|
|
99
|
+
return -1;
|
|
100
|
+
}
|
|
101
|
+
return (b.identifiers.length + b.references.length) - (a.identifiers.length + a.references.length);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
for (let variable of scope.variables) {
|
|
105
|
+
var name = "$$var$" + utils.hash(variable) + "$" + variable.name;
|
|
106
|
+
|
|
107
|
+
if (variable.tainted) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (variable.identifiers.length === 0) {
|
|
112
|
+
// do not change names since this is a special name
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (let def of variable.identifiers) {
|
|
117
|
+
// change definition's name
|
|
118
|
+
def.name = name;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (let ref of variable.references) {
|
|
122
|
+
// change reference's name
|
|
123
|
+
ref.identifier.name = name;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Replaces direct parameter references like
|
|
132
|
+
* function (a) {
|
|
133
|
+
* return a;
|
|
134
|
+
* }
|
|
135
|
+
* to copys like
|
|
136
|
+
* function (a) {
|
|
137
|
+
* var $$arg$abc = a;
|
|
138
|
+
* return $$arg$abc;
|
|
139
|
+
* }
|
|
140
|
+
* @param {Node} ast Root node
|
|
141
|
+
* @param {ScopeManager} scopeManager Scope manager
|
|
142
|
+
*/
|
|
143
|
+
redefineParameters (ast, scopeManager) {
|
|
144
|
+
function getArgumentIndex(method, identifier) {
|
|
145
|
+
assert(method.type == "FunctionDeclaration" || method.type == "FunctionExpression");
|
|
146
|
+
assert(identifier.type == "Identifier");
|
|
147
|
+
for (var i = 0; i < method.params.length; ++i) {
|
|
148
|
+
if (method.params[i].name == identifier.name) {
|
|
149
|
+
return i;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return -1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var rng = new utils.UniqueRandomAlpha(3);
|
|
156
|
+
|
|
157
|
+
scopeManager.scopes.forEach(scope => {
|
|
158
|
+
scope.variables.forEach(variable => {
|
|
159
|
+
variable.defs.forEach(def => {
|
|
160
|
+
if (def.type == "Parameter") {
|
|
161
|
+
assert(def.name.type == "Identifier");
|
|
162
|
+
var name = "$$arg$" + rng.get();
|
|
163
|
+
|
|
164
|
+
this.esutils.insertIntoScope(scope, {
|
|
165
|
+
type: "VariableDeclaration",
|
|
166
|
+
kind: "var",
|
|
167
|
+
declarations: [
|
|
168
|
+
{
|
|
169
|
+
type: "VariableDeclarator",
|
|
170
|
+
id: { type: "Identifier", name: name },
|
|
171
|
+
init: def.name
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
variable.references.forEach(reference => {
|
|
177
|
+
reference.identifier.name = name;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
};
|
package/toildefender.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
if (require.main === module) {
|
|
4
|
-
require("./cli").run();
|
|
5
|
-
} else {
|
|
6
|
-
module.exports = require("./obfuscator");
|
|
7
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
if (require.main === module) {
|
|
4
|
+
require("./cli").run();
|
|
5
|
+
} else {
|
|
6
|
+
module.exports = require("./obfuscator");
|
|
7
|
+
}
|
package/traverser.js
CHANGED
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var assert = require("assert");
|
|
4
|
-
|
|
5
|
-
var estraverse = require("estraverse");
|
|
6
|
-
|
|
7
|
-
var estest = require("./estest");
|
|
8
|
-
var utils = require("./utils");
|
|
9
|
-
|
|
10
|
-
// Depth-first
|
|
11
|
-
exports.traverse = function (node, stack, processor) {
|
|
12
|
-
assert.ok(estest.isNode(node));
|
|
13
|
-
assert.ok(Array.isArray(stack));
|
|
14
|
-
assert.equal(typeof processor, "function");
|
|
15
|
-
|
|
16
|
-
exports.visitChildren(node, (child, key) => {
|
|
17
|
-
return exports.traverse(child, [ { node: node, key: key } ].concat(stack), processor);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
return processor(node, [ { node: node } ].concat(stack));
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Breadth-first
|
|
24
|
-
exports.traverseEx = function (node, stack, processor) {
|
|
25
|
-
assert.ok(estest.isNode(node));
|
|
26
|
-
assert.ok(Array.isArray(stack));
|
|
27
|
-
assert.equal(typeof processor, "function");
|
|
28
|
-
|
|
29
|
-
var abort = false;
|
|
30
|
-
var controller = {
|
|
31
|
-
abort: function() {
|
|
32
|
-
abort = true;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
var queue = [];
|
|
37
|
-
exports.visitChildrenEx(node, (child, key) => {
|
|
38
|
-
var repl = processor.call(controller, child, [ { node: node } ].concat(stack));
|
|
39
|
-
if (repl == child) {
|
|
40
|
-
queue.push({
|
|
41
|
-
child: child,
|
|
42
|
-
key: key
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
return repl;
|
|
46
|
-
});
|
|
47
|
-
if (!abort) {
|
|
48
|
-
queue.every(elem => {
|
|
49
|
-
exports.traverseEx.call(controller, elem.child, [ { node: node, key: elem.key } ].concat(stack), processor);
|
|
50
|
-
return !abort;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return node;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
exports.visitChildren = function (node, processor) {
|
|
57
|
-
assert.ok(estest.isNode(node));
|
|
58
|
-
assert.equal(typeof processor, "function");
|
|
59
|
-
|
|
60
|
-
var keys = estraverse.VisitorKeys[node.type] || [];
|
|
61
|
-
keys.forEach(key => {
|
|
62
|
-
if (Array.isArray(node[key])) {
|
|
63
|
-
node[key] = node[key].map(x => {
|
|
64
|
-
if (x == null) {
|
|
65
|
-
return x;
|
|
66
|
-
}
|
|
67
|
-
var repl = processor(x, key);
|
|
68
|
-
assert(repl);
|
|
69
|
-
return repl;
|
|
70
|
-
});
|
|
71
|
-
} else if (node[key]) {
|
|
72
|
-
var repl = processor(node[key], key);
|
|
73
|
-
assert(repl);
|
|
74
|
-
node[key] = repl;
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
exports.visitChildrenEx = function (node, processor) {
|
|
80
|
-
assert.ok(estest.isNode(node));
|
|
81
|
-
assert.equal(typeof processor, "function");
|
|
82
|
-
|
|
83
|
-
var keys = estraverse.VisitorKeys[node.type] || [];
|
|
84
|
-
keys.forEach(key => {
|
|
85
|
-
if (Array.isArray(node[key])) {
|
|
86
|
-
let i = node[key].length;
|
|
87
|
-
while (i--) {
|
|
88
|
-
if (node[key][i] == null) {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
let replacement = processor(node[key][i], key);
|
|
92
|
-
assert(replacement);
|
|
93
|
-
if (replacement.length == 1) {
|
|
94
|
-
replacement = replacement[0];
|
|
95
|
-
}
|
|
96
|
-
if (Array.isArray(replacement)) {
|
|
97
|
-
utils.splice(node[key], i, 1, replacement);
|
|
98
|
-
} else {
|
|
99
|
-
node[key][i] = replacement;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
} else if (node[key]) {
|
|
103
|
-
let replacement = processor(node[key], key);
|
|
104
|
-
assert(replacement);
|
|
105
|
-
if (replacement.length == 1) {
|
|
106
|
-
replacement = replacement[0];
|
|
107
|
-
}
|
|
108
|
-
if (Array.isArray(replacement)) {
|
|
109
|
-
throw new Error("Cannot use array here: " + node.type + "." + key + "\n" + JSON.stringify(node) + "\n" + JSON.stringify(replacement));
|
|
110
|
-
} else {
|
|
111
|
-
node[key] = replacement;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
|
|
5
|
+
var estraverse = require("estraverse");
|
|
6
|
+
|
|
7
|
+
var estest = require("./estest");
|
|
8
|
+
var utils = require("./utils");
|
|
9
|
+
|
|
10
|
+
// Depth-first
|
|
11
|
+
exports.traverse = function (node, stack, processor) {
|
|
12
|
+
assert.ok(estest.isNode(node));
|
|
13
|
+
assert.ok(Array.isArray(stack));
|
|
14
|
+
assert.equal(typeof processor, "function");
|
|
15
|
+
|
|
16
|
+
exports.visitChildren(node, (child, key) => {
|
|
17
|
+
return exports.traverse(child, [ { node: node, key: key } ].concat(stack), processor);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return processor(node, [ { node: node } ].concat(stack));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Breadth-first
|
|
24
|
+
exports.traverseEx = function (node, stack, processor) {
|
|
25
|
+
assert.ok(estest.isNode(node));
|
|
26
|
+
assert.ok(Array.isArray(stack));
|
|
27
|
+
assert.equal(typeof processor, "function");
|
|
28
|
+
|
|
29
|
+
var abort = false;
|
|
30
|
+
var controller = {
|
|
31
|
+
abort: function() {
|
|
32
|
+
abort = true;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
var queue = [];
|
|
37
|
+
exports.visitChildrenEx(node, (child, key) => {
|
|
38
|
+
var repl = processor.call(controller, child, [ { node: node } ].concat(stack));
|
|
39
|
+
if (repl == child) {
|
|
40
|
+
queue.push({
|
|
41
|
+
child: child,
|
|
42
|
+
key: key
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return repl;
|
|
46
|
+
});
|
|
47
|
+
if (!abort) {
|
|
48
|
+
queue.every(elem => {
|
|
49
|
+
exports.traverseEx.call(controller, elem.child, [ { node: node, key: elem.key } ].concat(stack), processor);
|
|
50
|
+
return !abort;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return node;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
exports.visitChildren = function (node, processor) {
|
|
57
|
+
assert.ok(estest.isNode(node));
|
|
58
|
+
assert.equal(typeof processor, "function");
|
|
59
|
+
|
|
60
|
+
var keys = estraverse.VisitorKeys[node.type] || [];
|
|
61
|
+
keys.forEach(key => {
|
|
62
|
+
if (Array.isArray(node[key])) {
|
|
63
|
+
node[key] = node[key].map(x => {
|
|
64
|
+
if (x == null) {
|
|
65
|
+
return x;
|
|
66
|
+
}
|
|
67
|
+
var repl = processor(x, key);
|
|
68
|
+
assert(repl);
|
|
69
|
+
return repl;
|
|
70
|
+
});
|
|
71
|
+
} else if (node[key]) {
|
|
72
|
+
var repl = processor(node[key], key);
|
|
73
|
+
assert(repl);
|
|
74
|
+
node[key] = repl;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
exports.visitChildrenEx = function (node, processor) {
|
|
80
|
+
assert.ok(estest.isNode(node));
|
|
81
|
+
assert.equal(typeof processor, "function");
|
|
82
|
+
|
|
83
|
+
var keys = estraverse.VisitorKeys[node.type] || [];
|
|
84
|
+
keys.forEach(key => {
|
|
85
|
+
if (Array.isArray(node[key])) {
|
|
86
|
+
let i = node[key].length;
|
|
87
|
+
while (i--) {
|
|
88
|
+
if (node[key][i] == null) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
let replacement = processor(node[key][i], key);
|
|
92
|
+
assert(replacement);
|
|
93
|
+
if (replacement.length == 1) {
|
|
94
|
+
replacement = replacement[0];
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(replacement)) {
|
|
97
|
+
utils.splice(node[key], i, 1, replacement);
|
|
98
|
+
} else {
|
|
99
|
+
node[key][i] = replacement;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else if (node[key]) {
|
|
103
|
+
let replacement = processor(node[key], key);
|
|
104
|
+
assert(replacement);
|
|
105
|
+
if (replacement.length == 1) {
|
|
106
|
+
replacement = replacement[0];
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(replacement)) {
|
|
109
|
+
throw new Error("Cannot use array here: " + node.type + "." + key + "\n" + JSON.stringify(node) + "\n" + JSON.stringify(replacement));
|
|
110
|
+
} else {
|
|
111
|
+
node[key] = replacement;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
};
|