Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81158 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*/
9
/*global exports:true*/
10
"use strict";
11
12
var Syntax = require('jstransform').Syntax;
13
var utils = require('jstransform/src/utils');
14
15
var FALLBACK_TAGS = require('./xjs').knownTags;
16
var renderXJSExpressionContainer =
17
require('./xjs').renderXJSExpressionContainer;
18
var renderXJSLiteral = require('./xjs').renderXJSLiteral;
19
var quoteAttrName = require('./xjs').quoteAttrName;
20
21
var trimLeft = require('./xjs').trimLeft;
22
23
/**
24
* Customized desugar processor for React JSX. Currently:
25
*
26
* <X> </X> => React.createElement(X, null)
27
* <X prop="1" /> => React.createElement(X, {prop: '1'}, null)
28
* <X prop="2"><Y /></X> => React.createElement(X, {prop:'2'},
29
* React.createElement(Y, null)
30
* )
31
* <div /> => React.createElement("div", null)
32
*/
33
34
/**
35
* Removes all non-whitespace/parenthesis characters
36
*/
37
var reNonWhiteParen = /([^\s\(\)])/g;
38
function stripNonWhiteParen(value) {
39
return value.replace(reNonWhiteParen, '');
40
}
41
42
var tagConvention = /^[a-z]|\-/;
43
function isTagName(name) {
44
return tagConvention.test(name);
45
}
46
47
function visitReactTag(traverse, object, path, state) {
48
var openingElement = object.openingElement;
49
var nameObject = openingElement.name;
50
var attributesObject = openingElement.attributes;
51
52
utils.catchup(openingElement.range[0], state, trimLeft);
53
54
if (nameObject.type === Syntax.XJSNamespacedName && nameObject.namespace) {
55
throw new Error('Namespace tags are not supported. ReactJSX is not XML.');
56
}
57
58
// We assume that the React runtime is already in scope
59
utils.append('React.createElement(', state);
60
61
// Identifiers with lower case or hypthens are fallback tags (strings).
62
// XJSMemberExpressions are not.
63
if (nameObject.type === Syntax.XJSIdentifier && isTagName(nameObject.name)) {
64
// This is a temporary error message to assist upgrades
65
if (!FALLBACK_TAGS.hasOwnProperty(nameObject.name)) {
66
throw new Error(
67
'Lower case component names (' + nameObject.name + ') are no longer ' +
68
'supported in JSX: See http://fb.me/react-jsx-lower-case'
69
);
70
}
71
72
utils.append('"' + nameObject.name + '"', state);
73
utils.move(nameObject.range[1], state);
74
} else {
75
// Use utils.catchup in this case so we can easily handle
76
// XJSMemberExpressions which look like Foo.Bar.Baz. This also handles
77
// XJSIdentifiers that aren't fallback tags.
78
utils.move(nameObject.range[0], state);
79
utils.catchup(nameObject.range[1], state);
80
}
81
82
utils.append(', ', state);
83
84
var hasAttributes = attributesObject.length;
85
86
var hasAtLeastOneSpreadProperty = attributesObject.some(function(attr) {
87
return attr.type === Syntax.XJSSpreadAttribute;
88
});
89
90
// if we don't have any attributes, pass in null
91
if (hasAtLeastOneSpreadProperty) {
92
utils.append('React.__spread({', state);
93
} else if (hasAttributes) {
94
utils.append('{', state);
95
} else {
96
utils.append('null', state);
97
}
98
99
// keep track of if the previous attribute was a spread attribute
100
var previousWasSpread = false;
101
102
// write attributes
103
attributesObject.forEach(function(attr, index) {
104
var isLast = index === attributesObject.length - 1;
105
106
if (attr.type === Syntax.XJSSpreadAttribute) {
107
// Close the previous object or initial object
108
if (!previousWasSpread) {
109
utils.append('}, ', state);
110
}
111
112
// Move to the expression start, ignoring everything except parenthesis
113
// and whitespace.
114
utils.catchup(attr.range[0], state, stripNonWhiteParen);
115
// Plus 1 to skip `{`.
116
utils.move(attr.range[0] + 1, state);
117
utils.catchup(attr.argument.range[0], state, stripNonWhiteParen);
118
119
traverse(attr.argument, path, state);
120
121
utils.catchup(attr.argument.range[1], state);
122
123
// Move to the end, ignoring parenthesis and the closing `}`
124
utils.catchup(attr.range[1] - 1, state, stripNonWhiteParen);
125
126
if (!isLast) {
127
utils.append(', ', state);
128
}
129
130
utils.move(attr.range[1], state);
131
132
previousWasSpread = true;
133
134
return;
135
}
136
137
// If the next attribute is a spread, we're effective last in this object
138
if (!isLast) {
139
isLast = attributesObject[index + 1].type === Syntax.XJSSpreadAttribute;
140
}
141
142
if (attr.name.namespace) {
143
throw new Error(
144
'Namespace attributes are not supported. ReactJSX is not XML.');
145
}
146
var name = attr.name.name;
147
148
utils.catchup(attr.range[0], state, trimLeft);
149
150
if (previousWasSpread) {
151
utils.append('{', state);
152
}
153
154
utils.append(quoteAttrName(name), state);
155
utils.append(': ', state);
156
157
if (!attr.value) {
158
state.g.buffer += 'true';
159
state.g.position = attr.name.range[1];
160
if (!isLast) {
161
utils.append(', ', state);
162
}
163
} else {
164
utils.move(attr.name.range[1], state);
165
// Use catchupNewlines to skip over the '=' in the attribute
166
utils.catchupNewlines(attr.value.range[0], state);
167
if (attr.value.type === Syntax.Literal) {
168
renderXJSLiteral(attr.value, isLast, state);
169
} else {
170
renderXJSExpressionContainer(traverse, attr.value, isLast, path, state);
171
}
172
}
173
174
utils.catchup(attr.range[1], state, trimLeft);
175
176
previousWasSpread = false;
177
178
});
179
180
if (!openingElement.selfClosing) {
181
utils.catchup(openingElement.range[1] - 1, state, trimLeft);
182
utils.move(openingElement.range[1], state);
183
}
184
185
if (hasAttributes && !previousWasSpread) {
186
utils.append('}', state);
187
}
188
189
if (hasAtLeastOneSpreadProperty) {
190
utils.append(')', state);
191
}
192
193
// filter out whitespace
194
var childrenToRender = object.children.filter(function(child) {
195
return !(child.type === Syntax.Literal
196
&& typeof child.value === 'string'
197
&& child.value.match(/^[ \t]*[\r\n][ \t\r\n]*$/));
198
});
199
if (childrenToRender.length > 0) {
200
var lastRenderableIndex;
201
202
childrenToRender.forEach(function(child, index) {
203
if (child.type !== Syntax.XJSExpressionContainer ||
204
child.expression.type !== Syntax.XJSEmptyExpression) {
205
lastRenderableIndex = index;
206
}
207
});
208
209
if (lastRenderableIndex !== undefined) {
210
utils.append(', ', state);
211
}
212
213
childrenToRender.forEach(function(child, index) {
214
utils.catchup(child.range[0], state, trimLeft);
215
216
var isLast = index >= lastRenderableIndex;
217
218
if (child.type === Syntax.Literal) {
219
renderXJSLiteral(child, isLast, state);
220
} else if (child.type === Syntax.XJSExpressionContainer) {
221
renderXJSExpressionContainer(traverse, child, isLast, path, state);
222
} else {
223
traverse(child, path, state);
224
if (!isLast) {
225
utils.append(', ', state);
226
}
227
}
228
229
utils.catchup(child.range[1], state, trimLeft);
230
});
231
}
232
233
if (openingElement.selfClosing) {
234
// everything up to />
235
utils.catchup(openingElement.range[1] - 2, state, trimLeft);
236
utils.move(openingElement.range[1], state);
237
} else {
238
// everything up to </ sdflksjfd>
239
utils.catchup(object.closingElement.range[0], state, trimLeft);
240
utils.move(object.closingElement.range[1], state);
241
}
242
243
utils.append(')', state);
244
return false;
245
}
246
247
visitReactTag.test = function(object, path, state) {
248
return object.type === Syntax.XJSElement;
249
};
250
251
exports.visitorList = [
252
visitReactTag
253
];
254
255