-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
135 lines (117 loc) · 4.82 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
const AEXPR_IDENTIFIER_NAME = 'aexpr';
const FLAG_SHOULD_NOT_REWRITE_IDENTIFIER = Symbol('FLAG: should not rewrite identifier');
export default function({ types: t, template, traverse }) {
const GENERATED_IMPORT_IDENTIFIER = Symbol("generated import identifier");
function addCustomTemplate(file, name) {
let declar = file.declarations[name];
if (declar) return declar;
let identifier = file.declarations[name] = file.addImport("active-expression-proxies", name, name);
identifier[GENERATED_IMPORT_IDENTIFIER] = true;
identifier[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER] = true;
return identifier;
}
return {
visitor: {
Program: {
enter(path, state) {
function hasDirective(path, name) {
let foundDirective = false;
path.traverse({
Directive(path) {
if(path.get("value").node.value === name) {
foundDirective = true;
}
}
});
return foundDirective;
}
function shouldTransform() {
const proxyDirective = hasDirective(path, 'use proxies for aexprs');
const proxyPreference = true;
const inWorkspace = state.opts.executedIn === 'workspace';
const inFile = state.opts.executedIn === 'file';
if (inWorkspace) {
return proxyPreference;
} else if (inFile) {
return proxyDirective;
}
return true;
// throw new Error('This should not be possible');
}
if (!shouldTransform()) { return; }
function replaceNode(path, wrapType, unwrap = false) {
// do not wrap the same object twice
if (path.node.__already_transformed__) { return; }
path.node.__already_transformed__ = true;
let transformed;
if (unwrap){
transformed = t.callExpression(
addCustomTemplate(state.file, 'unwrap'), [path.node])
} else {
transformed = t.callExpression(
addCustomTemplate(state.file, 'wrap'), [t.stringLiteral(wrapType),path.node]
);
}
path.replaceWith(transformed);
}
path.traverse({
ObjectExpression(path) {
// do not replace objects in calls to Object.defineProperty
try {
if(path.parent.callee.property.name === "defineProperty") {
return;
}
} catch(e) {
// Once we can use the new ecma script2020 syntax this try-catch can be replaced by optional chaining
// https://iolap.com/2019/09/27/whats-next-for-javascript-top-5-new-features-for-2020/
}
replaceNode(path, 'Object')
},
CallExpression(path) {
// unwrap proxies in Object.defineProperty
try {
if(path.node.callee.property.name === "defineProperty" && path.node.arguments[2].type === "Identifier") {
path.node.arguments[2].__should_unwrap__ = true;
}
} catch(e) {
// Once we can use the new ecma script2020 syntax this try-catch can be replaced by optional chaining
// https://iolap.com/2019/09/27/whats-next-for-javascript-top-5-new-features-for-2020/
}
},
NewExpression(path) {
replaceNode(path, path.node.callee.name)
},
ArrayExpression(path) {
replaceNode(path, 'Array')
},
Identifier(path) {
if (path.node[FLAG_SHOULD_NOT_REWRITE_IDENTIFIER]) {
return;
}
// Check for a call to undeclared aexpr:
if (
t.isCallExpression(path.parent) &&
path.node.name === AEXPR_IDENTIFIER_NAME &&
!path.scope.hasBinding(AEXPR_IDENTIFIER_NAME)
) {
//logIdentifier("call to aexpr", path);
path.replaceWith(
addCustomTemplate(state.file, AEXPR_IDENTIFIER_NAME)
);
return;
}
try {
if(path.parent.callee.property.name === "defineProperty" && path.parent.arguments[2] === path.node) {
replaceNode(path, 'Proxy', true);
}
} catch(e) {
// Once we can use the new ecma script2020 syntax this try-catch can be replaced by optional chaining
// https://iolap.com/2019/09/27/whats-next-for-javascript-top-5-new-features-for-2020/
}
}
})
}
}
}
};
}