Skip to content

Commit 08e361a

Browse files
committed
Add --jsx transform
React is a special case of it.
1 parent 6be0206 commit 08e361a

File tree

5 files changed

+122
-28
lines changed

5 files changed

+122
-28
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ namespace ts {
435435
* Get symbols that represent parameter-property-declaration as parameter and as property declaration
436436
* @param parameter a parameterDeclaration node
437437
* @param parameterName a name of the parameter to get the symbols for.
438-
* @return a tuple of two symbols
438+
* @return a tuple of two symbols
439439
*/
440440
function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): [Symbol, Symbol] {
441441
const constructoDeclaration = parameter.parent;
@@ -8363,13 +8363,27 @@ namespace ts {
83638363
checkGrammarJsxElement(node);
83648364
checkJsxPreconditions(node);
83658365

8366-
// If we're compiling under --jsx react, the symbol 'React' should
8367-
// be marked as 'used' so we don't incorrectly elide its import. And if there
8368-
// is no 'React' symbol in scope, we should issue an error.
8366+
/**
8367+
* Mark symbol as 'used' so we don't incorreclty elide its import.
8368+
* If the symbol is not found in scope, we should issue an error.
8369+
*/
8370+
function markReferenced(symbolName: string) {
8371+
const symbol = resolveName(node.tagName, symbolName, SymbolFlags.Value, Diagnostics.Cannot_find_name_0, symbolName);
8372+
if (symbol) {
8373+
getSymbolLinks(symbol).referenced = true;
8374+
}
8375+
}
8376+
83698377
if (compilerOptions.jsx === JsxEmit.React) {
8370-
const reactSym = resolveName(node.tagName, "React", SymbolFlags.Value, Diagnostics.Cannot_find_name_0, "React");
8371-
if (reactSym) {
8372-
getSymbolLinks(reactSym).referenced = true;
8378+
markReferenced("React");
8379+
}
8380+
else if (compilerOptions.jsx === JsxEmit.Transform) {
8381+
if (compilerOptions.jsxNamespace !== "") {
8382+
markReferenced(compilerOptions.jsxNamespace || "JSX");
8383+
}
8384+
else {
8385+
markReferenced(compilerOptions.jsxComposeFunction || "compose");
8386+
markReferenced(compilerOptions.jsxSpreadFunction || "spread");
83738387
}
83748388
}
83758389

src/compiler/commandLineParser.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,32 @@ namespace ts {
4848
name: "jsx",
4949
type: {
5050
"preserve": JsxEmit.Preserve,
51-
"react": JsxEmit.React
51+
"react": JsxEmit.React,
52+
"transform": JsxEmit.Transform
5253
},
5354
paramType: Diagnostics.KIND,
54-
description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react,
55-
error: Diagnostics.Argument_for_jsx_must_be_preserve_or_react
55+
description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_transform_or_react,
56+
error: Diagnostics.Argument_for_jsx_must_be_preserve_transform_or_react
57+
},
58+
{
59+
name: "jsxNamespace",
60+
type: "string",
61+
description: Diagnostics.JSX_transform_Colon_Namespace_Default_to_JSX
62+
},
63+
{
64+
name: "jsxComposeFunction",
65+
type: "string",
66+
description: Diagnostics.JSX_transform_Colon_Compose_function_Default_to_compose
67+
},
68+
{
69+
name: "jsxSpreadFunction",
70+
type: "string",
71+
description: Diagnostics.JSX_transform_Colon_Spread_function_Default_to_spread
72+
},
73+
{
74+
name: "jsxChildrenAsArray",
75+
type: "boolean",
76+
description: Diagnostics.JSX_transform_Colon_Pass_children_as_array_Default_to_false
5677
},
5778
{
5879
name: "listFiles",
@@ -514,7 +535,7 @@ namespace ts {
514535
for (const extension of supportedExtensions) {
515536
const filesInDirWithExtension = host.readDirectory(basePath, extension, exclude);
516537
for (const fileName of filesInDirWithExtension) {
517-
// .ts extension would read the .d.ts extension files too but since .d.ts is lower priority extension,
538+
// .ts extension would read the .d.ts extension files too but since .d.ts is lower priority extension,
518539
// lets pick them when its turn comes up
519540
if (extension === ".ts" && fileExtensionIs(fileName, ".d.ts")) {
520541
continue;

src/compiler/diagnosticMessages.json

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@
798798
"A decorator can only decorate a method implementation, not an overload.": {
799799
"category": "Error",
800800
"code": 1249
801-
},
801+
},
802802
"'with' statements are not allowed in an async function block.": {
803803
"category": "Error",
804804
"code": 1300
@@ -2377,12 +2377,12 @@
23772377
"category": "Message",
23782378
"code": 6078
23792379
},
2380-
"Specify JSX code generation: 'preserve' or 'react'": {
2380+
"Specify JSX code generation: 'preserve', 'transform', or 'react'": {
23812381
"category": "Message",
23822382
"code": 6080
23832383
},
2384-
"Argument for '--jsx' must be 'preserve' or 'react'.": {
2385-
"category": "Message",
2384+
"Argument for '--jsx' must be 'preserve', 'transform', or 'react'.": {
2385+
"category": "Error",
23862386
"code": 6081
23872387
},
23882388
"Only 'amd' and 'system' modules are supported alongside --{0}.": {
@@ -2393,7 +2393,22 @@
23932393
"category": "Message",
23942394
"code": 6083
23952395
},
2396-
2396+
"JSX transform: Namespace. Default to 'JSX'": {
2397+
"category": "Message",
2398+
"code": 6084
2399+
},
2400+
"JSX transform: Compose function. Default to 'compose'": {
2401+
"category": "Message",
2402+
"code": 6085
2403+
},
2404+
"JSX transform: Spread function. Default to 'spread'": {
2405+
"category": "Message",
2406+
"code": 6086
2407+
},
2408+
"JSX transform: Pass children as array. Default to false.": {
2409+
"category": "Message",
2410+
"code": 6087
2411+
},
23972412
"Variable '{0}' implicitly has an '{1}' type.": {
23982413
"category": "Error",
23992414
"code": 7005

src/compiler/emitter.ts

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,9 +1144,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
11441144
emit(span.literal);
11451145
}
11461146

1147-
function jsxEmitReact(node: JsxElement | JsxSelfClosingElement) {
1147+
function jsxEmitTransform(node: JsxElement | JsxSelfClosingElement, jsxOptions: any) {
11481148
/// Emit a tag name, which is either '"div"' for lower-cased names, or
11491149
/// 'Div' for upper-cased or dotted names
1150+
/// May add an option to disable this as generic transform may not follow this convension
11501151
function emitTagName(name: Identifier | QualifiedName) {
11511152
if (name.kind === SyntaxKind.Identifier && isIntrinsicJsxName((<Identifier>name).text)) {
11521153
write("\"");
@@ -1186,28 +1187,29 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
11861187

11871188
function emitJsxElement(openingNode: JsxOpeningLikeElement, children?: JsxChild[]) {
11881189
const syntheticReactRef = <Identifier>createSynthesizedNode(SyntaxKind.Identifier);
1189-
syntheticReactRef.text = "React";
1190+
syntheticReactRef.text = jsxOptions.namespace;
11901191
syntheticReactRef.parent = openingNode;
11911192

1192-
// Call React.createElement(tag, ...
1193+
// Call React.createElement(tag, ... or JSX.compose(tag,...
11931194
emitLeadingComments(openingNode);
11941195
emitExpressionIdentifier(syntheticReactRef);
1195-
write(".createElement(");
1196+
write(`.${jsxOptions.composeFunction}(`);
11961197
emitTagName(openingNode.tagName);
11971198
write(", ");
11981199

11991200
// Attribute list
12001201
if (openingNode.attributes.length === 0) {
12011202
// When there are no attributes, React wants "null"
1203+
// May need to add an jsxOption to override this behavior.
12021204
write("null");
12031205
}
12041206
else {
12051207
// Either emit one big object literal (no spread attribs), or
1206-
// a call to React.__spread
1208+
// a call to React.__spread or JSX.spread
12071209
const attrs = openingNode.attributes;
12081210
if (forEach(attrs, attr => attr.kind === SyntaxKind.JsxSpreadAttribute)) {
12091211
emitExpressionIdentifier(syntheticReactRef);
1210-
write(".__spread(");
1212+
write(`.${jsxOptions.spreadFunction}(`);
12111213

12121214
let haveOpenedObjectLiteral = false;
12131215
for (let i = 0; i < attrs.length; i++) {
@@ -1243,7 +1245,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
12431245
}
12441246
if (haveOpenedObjectLiteral) write("}");
12451247

1246-
write(")"); // closing paren to React.__spread(
1248+
write(")"); // closing paren to React.__spread( or JSX.spread(
12471249
}
12481250
else {
12491251
// One object literal with all the attributes in them
@@ -1260,6 +1262,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
12601262

12611263
// Children
12621264
if (children) {
1265+
let skipFirstComma = false;
1266+
if (jsxOptions.childrenAsArray) {
1267+
write(", [");
1268+
skipFirstComma = true;
1269+
}
1270+
12631271
for (var i = 0; i < children.length; i++) {
12641272
// Don't emit empty expressions
12651273
if (children[i].kind === SyntaxKind.JsxExpression && !((<JsxExpression>children[i]).expression)) {
@@ -1270,21 +1278,36 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
12701278
if (children[i].kind === SyntaxKind.JsxText) {
12711279
const text = getTextToEmit(<JsxText>children[i]);
12721280
if (text !== undefined) {
1273-
write(", \"");
1281+
if (skipFirstComma) {
1282+
skipFirstComma = false;
1283+
}
1284+
else {
1285+
write(", ");
1286+
}
1287+
1288+
write("\"");
12741289
write(text);
12751290
write("\"");
12761291
}
12771292
}
12781293
else {
1279-
write(", ");
1294+
if (skipFirstComma) {
1295+
skipFirstComma = false;
1296+
}
1297+
else {
1298+
write(", ");
1299+
}
12801300
emit(children[i]);
12811301
}
12821302

12831303
}
1304+
if (jsxOptions.childrenAsArray) {
1305+
write("]");
1306+
}
12841307
}
12851308

12861309
// Closing paren
1287-
write(")"); // closes "React.createElement("
1310+
write(")"); // closes "React.createElement(" or "JSX.compose("
12881311
emitTrailingComments(openingNode);
12891312
}
12901313

@@ -7176,7 +7199,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
71767199
function emitJsxElement(node: JsxElement | JsxSelfClosingElement) {
71777200
switch (compilerOptions.jsx) {
71787201
case JsxEmit.React:
7179-
jsxEmitReact(node);
7202+
jsxEmitTransform(node, {
7203+
namespace: "React",
7204+
composeFunction: "createElement",
7205+
spreadFunction: "__spread",
7206+
childrenAsArray: false
7207+
});
7208+
break;
7209+
case JsxEmit.Transform:
7210+
jsxEmitTransform(node, {
7211+
namespace: compilerOptions.jsxNamespace || "JSX",
7212+
composeFunction: compilerOptions.jsxComposeFunction || "compose",
7213+
spreadFunction: compilerOptions.jsxSpreadFunction || "spread",
7214+
childrenAsArray: compilerOptions.jsxChildrenAsArray || false
7215+
});
71807216
break;
71817217
case JsxEmit.Preserve:
71827218
// Fall back to preserve if None was specified (we'll error earlier)
@@ -7237,6 +7273,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
72377273
function getTextToEmit(node: JsxText) {
72387274
switch (compilerOptions.jsx) {
72397275
case JsxEmit.React:
7276+
case JsxEmit.Transform:
72407277
let text = trimReactWhitespaceAndApplyEntities(node);
72417278
if (text === undefined || text.length === 0) {
72427279
return undefined;
@@ -7253,6 +7290,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
72537290
function emitJsxText(node: JsxText) {
72547291
switch (compilerOptions.jsx) {
72557292
case JsxEmit.React:
7293+
case JsxEmit.Transform:
72567294
write("\"");
72577295
write(trimReactWhitespaceAndApplyEntities(node));
72587296
write("\"");
@@ -7275,6 +7313,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
72757313
write("}");
72767314
break;
72777315
case JsxEmit.React:
7316+
case JsxEmit.Transform:
72787317
emit(node.expression);
72797318
break;
72807319
}

src/compiler/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2382,6 +2382,10 @@ namespace ts {
23822382
inlineSourceMap?: boolean;
23832383
inlineSources?: boolean;
23842384
jsx?: JsxEmit;
2385+
jsxNamespace?: string;
2386+
jsxComposeFunction?: string;
2387+
jsxSpreadFunction?: string;
2388+
jsxChildrenAsArray?: boolean;
23852389
listFiles?: boolean;
23862390
locale?: string;
23872391
mapRoot?: string;
@@ -2441,7 +2445,8 @@ namespace ts {
24412445
export const enum JsxEmit {
24422446
None = 0,
24432447
Preserve = 1,
2444-
React = 2
2448+
React = 2,
2449+
Transform = 3
24452450
}
24462451

24472452
export const enum NewLineKind {

0 commit comments

Comments
 (0)