@@ -7,6 +7,7 @@ var recast = require('recast');
7
7
var types = recast . types ;
8
8
var n = types . namedTypes ;
9
9
var b = types . builders ;
10
+ var PathVisitor = types . PathVisitor ;
10
11
11
12
var util = require ( 'ast-util' ) ;
12
13
@@ -17,51 +18,104 @@ assert.ok(
17
18
18
19
/**
19
20
* Visits a node of an AST looking for arrow function expressions. This is
20
- * intended to be used with the ast-types `traverse ()` function.
21
+ * intended to be used with the ast-types `visit ()` function.
21
22
*
22
- * @param { Object } node
23
- * @this {ast-types.Path}
23
+ * @constructor
24
+ * @extends PathVisitor
24
25
*/
25
- function visitNode ( node ) {
26
- if ( ! n . ArrowFunctionExpression . check ( node ) ) {
27
- return ;
28
- }
26
+ function ArrowFunctionExpressionVisitor ( ) {
27
+ PathVisitor . call ( this ) ;
28
+ }
29
+ ArrowFunctionExpressionVisitor . prototype = Object . create ( PathVisitor . prototype ) ;
30
+ ArrowFunctionExpressionVisitor . prototype . constructor = ArrowFunctionExpressionVisitor ;
31
+
32
+ /**
33
+ * Visits arrow function expressions and replaces them with normal functions.
34
+ *
35
+ * @param {types.NodePath } path
36
+ * @return {?Node }
37
+ */
38
+ ArrowFunctionExpressionVisitor . prototype . visitArrowFunctionExpression = function ( path ) {
39
+ var node = path . node ;
40
+
41
+ this . traverse ( path ) ;
42
+
43
+ // In the future, ArrowFunctionExpression and FunctionExpression nodes may
44
+ // get new fields (like .async) that we can't anticipate yet, so we simply
45
+ // switch the type and let all the other fields carry over.
46
+ node . type = 'FunctionExpression' ;
29
47
30
48
if ( node . expression ) {
31
49
node . expression = false ;
32
50
node . body = b . blockStatement ( [ b . returnStatement ( node . body ) ] ) ;
33
51
}
34
52
35
- // In the future, ArrowFunctionExpression and FunctionExpression nodes
36
- // may get new fields (like .async) that we can't anticipate yet, so we
37
- // simply switch the type and let all the other fields carry over.
38
- node . type = 'FunctionExpression' ;
53
+ if ( node . hasThisExpression ) {
54
+ return b . callExpression (
55
+ b . memberExpression ( node , b . identifier ( 'bind' ) , false ) ,
56
+ [ b . thisExpression ( ) ]
57
+ ) ;
58
+ }
59
+ } ;
39
60
40
- var foundThisExpression = false ;
41
- var scope = this . scope . parent ;
61
+ /**
62
+ * Ensures that any arrow function directly containing `this` is appropriately
63
+ * marked as such.
64
+ *
65
+ * @param {types.NodePath } path
66
+ * @return {?Node }
67
+ */
68
+ ArrowFunctionExpressionVisitor . prototype . visitThisExpression = function ( path ) {
69
+ var arrowFnPath = this . associatedArrowFunctionPath ( path ) ;
70
+ if ( arrowFnPath ) {
71
+ arrowFnPath . node . hasThisExpression = true ;
72
+ }
73
+ this . traverse ( path ) ;
74
+ } ;
42
75
43
- types . traverse ( node . body , function ( child ) {
44
- // don't look inside non-arrow functions
45
- if ( n . Function . check ( child ) && ! n . ArrowFunctionExpression . check ( child ) ) {
46
- return false ;
47
- }
76
+ /**
77
+ * Ensures that `arguments` directly contained in arrow functions is hoisted.
78
+ *
79
+ * @param {types.NodePath } path
80
+ * @return {?Node }
81
+ */
82
+ ArrowFunctionExpressionVisitor . prototype . visitIdentifier = function ( path ) {
83
+ var node = path . node ;
48
84
49
- if ( n . ThisExpression . check ( child ) ) {
50
- foundThisExpression = true ;
85
+ if ( node . name === 'arguments' && util . isReference ( path ) ) {
86
+ var functionScope = this . associatedFunctionScope ( path ) ;
87
+ if ( functionScope ) {
88
+ return util . sharedFor ( functionScope , node . name ) ;
51
89
}
90
+ }
52
91
53
- if ( util . isReference ( this ) && child . name === 'arguments' ) {
54
- this . replace ( util . sharedFor ( scope , 'arguments' ) ) ;
55
- }
56
- } ) ;
92
+ this . traverse ( path ) ;
93
+ } ;
57
94
58
- if ( foundThisExpression ) {
59
- this . replace ( b . callExpression (
60
- b . memberExpression ( node , b . identifier ( 'bind' ) , false ) ,
61
- [ b . thisExpression ( ) ]
62
- ) ) ;
95
+ /**
96
+ * @private
97
+ * @param {types.NodePath } path
98
+ * @return {?types.NodePath } The arrow function directly `path`, if any.
99
+ */
100
+ ArrowFunctionExpressionVisitor . prototype . associatedArrowFunctionPath = function ( path ) {
101
+ var scope = path . scope ;
102
+ if ( n . ArrowFunctionExpression . check ( scope . path . node ) ) {
103
+ return scope . path ;
63
104
}
64
- }
105
+ } ;
106
+
107
+ /**
108
+ * @private
109
+ * @param {types.NodePath } path
110
+ * @return {?types.Scope } The nearest non-arrow function scope above `path`.
111
+ */
112
+ ArrowFunctionExpressionVisitor . prototype . associatedFunctionScope = function ( path ) {
113
+ var scope = path . scope ;
114
+ while ( scope && n . ArrowFunctionExpression . check ( scope . path . node ) ) {
115
+ scope = scope . parent ;
116
+ }
117
+ return scope ;
118
+ } ;
65
119
66
120
/**
67
121
* Transform an Esprima AST generated from ES6 by replacing all
@@ -77,7 +131,7 @@ function visitNode(node) {
77
131
* @return {Object }
78
132
*/
79
133
function transform ( ast ) {
80
- return types . traverse ( ast , visitNode ) ;
134
+ return types . visit ( ast , new ArrowFunctionExpressionVisitor ( ) ) ;
81
135
}
82
136
83
137
/**
@@ -87,6 +141,7 @@ function transform(ast) {
87
141
* compile('() => 42'); // 'function() { return 42; };'
88
142
*
89
143
* @param {string } source
144
+ * @param {object } mapOptions
90
145
* @return {string }
91
146
*/
92
147
function compile ( source , mapOptions ) {
0 commit comments