-
Notifications
You must be signed in to change notification settings - Fork 200
/
rewrite-namespace-this-references.ts
110 lines (103 loc) · 4.03 KB
/
rewrite-namespace-this-references.ts
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
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import * as astTypes from 'ast-types';
import {NodePath} from 'ast-types';
import * as estree from 'estree';
import * as jsc from 'jscodeshift';
/**
* Rewrite `this` references to the explicit namespaceReference identifier
* within a single BlockStatement if they are the start of a member expression,
* otherwise they are rewritten to `undefined`. Don't traverse deeper into new
* scopes.
*/
function rewriteSingleScopeThisReferences(
blockStatement: estree.BlockStatement, namespaceReference: string) {
astTypes.visit(blockStatement, {
visitThisExpression(path: NodePath<estree.ThisExpression>) {
const parent = path.parent;
if (parent && parent.node.type !== 'MemberExpression') {
// When a namespace object is itself referenced with `this` but isn't
// used to reference a member of the namespace, rewrite the `this` to
// `undefined`:
path.replace(jsc.identifier('undefined'));
} else {
path.replace(jsc.identifier(namespaceReference));
}
return false;
},
visitFunctionExpression(_path: NodePath<estree.FunctionExpression>) {
// Don't visit into new scopes
return false;
},
visitFunctionDeclaration(_path: NodePath<estree.FunctionDeclaration>) {
// Don't visit into new scopes
return false;
},
visitMethodDefinition(_path: NodePath) {
// Don't visit into new scopes
return false;
},
// Note: we do visit into ArrowFunctionExpressions because they
// inherit the containing `this` context.
});
}
/**
* Rewrite `this` references that refer to the namespace object. Replace
* with an explicit reference to the namespace. This simplifies the rest of
* our transform pipeline by letting it assume that all namespace references
* are explicit.
*
* NOTE(fks): References to the namespace object still need to be corrected
* after this step, so timing is important: Only run after exports have
* been created, but before all namespace references are corrected.
*/
function rewriteNamespaceThisReferences(
program: estree.Program, namespaceName: string) {
astTypes.visit(program, {
visitExportNamedDeclaration:
(path: NodePath<estree.ExportNamedDeclaration>) => {
if (path.node.declaration &&
path.node.declaration.type === 'FunctionDeclaration') {
rewriteSingleScopeThisReferences(
path.node.declaration.body, namespaceName);
}
return false;
},
visitExportDefaultDeclaration:
(path: NodePath<estree.ExportDefaultDeclaration>) => {
if (path.node.declaration &&
path.node.declaration.type === 'FunctionDeclaration') {
rewriteSingleScopeThisReferences(
path.node.declaration.body, namespaceName);
}
return false;
},
});
}
/**
* Rewrite `this` references that refer to the namespace object. Replace
* with an explicit reference to the namespace. This simplifies the rest of
* our transform pipeline by letting it assume that all namespace references
* are explicit.
*
* NOTE(fks): References to the namespace object still need to be corrected
* after this step, so timing is important: Only run after exports have
* been created, but before all namespace references are corrected.
*/
export function rewriteNamespacesThisReferences(
program: estree.Program, namespaceNames: Set<string>) {
for (const namespaceName of namespaceNames) {
rewriteNamespaceThisReferences(program, namespaceName);
}
}