• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    export function transformJsx(context: TransformationContext) {
4        interface PerFileState {
5            importSpecifier?: string;
6            filenameDeclaration?: VariableDeclaration & { name: Identifier; };
7            utilizedImplicitRuntimeImports?: Map<Map<ImportSpecifier>>;
8        }
9
10        const {
11            factory,
12            getEmitHelperFactory: emitHelpers,
13        } = context;
14        const compilerOptions = context.getCompilerOptions();
15        let currentSourceFile: SourceFile;
16        let currentFileState!: PerFileState;
17
18        return chainBundle(context, transformSourceFile);
19
20        function getCurrentFileNameExpression(): Identifier {
21            if (currentFileState.filenameDeclaration) {
22                return currentFileState.filenameDeclaration.name;
23            }
24            const declaration = factory.createVariableDeclaration(factory.createUniqueName("_jsxFileName", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel), /*exclaimationToken*/ undefined, /*type*/ undefined, factory.createStringLiteral(currentSourceFile.fileName));
25            currentFileState.filenameDeclaration = declaration as VariableDeclaration & { name: Identifier };
26            return currentFileState.filenameDeclaration.name;
27        }
28
29        function getJsxFactoryCalleePrimitive(isStaticChildren: boolean): "jsx" | "jsxs" | "jsxDEV" {
30            return compilerOptions.jsx === JsxEmit.ReactJSXDev ? "jsxDEV" : isStaticChildren ? "jsxs" : "jsx";
31        }
32
33        function getJsxFactoryCallee(isStaticChildren: boolean) {
34            const type = getJsxFactoryCalleePrimitive(isStaticChildren);
35            return getImplicitImportForName(type);
36        }
37
38        function getImplicitJsxFragmentReference() {
39            return getImplicitImportForName("Fragment");
40        }
41
42        function getImplicitImportForName(name: string) {
43            const importSource = name === "createElement"
44                ? currentFileState.importSpecifier!
45                : getJSXRuntimeImport(currentFileState.importSpecifier, compilerOptions)!;
46            const existing = currentFileState.utilizedImplicitRuntimeImports?.get(importSource)?.get(name);
47            if (existing) {
48                return existing.name;
49            }
50            if (!currentFileState.utilizedImplicitRuntimeImports) {
51                currentFileState.utilizedImplicitRuntimeImports = new Map();
52            }
53            let specifierSourceImports = currentFileState.utilizedImplicitRuntimeImports.get(importSource);
54            if (!specifierSourceImports) {
55                specifierSourceImports = new Map();
56                currentFileState.utilizedImplicitRuntimeImports.set(importSource, specifierSourceImports);
57            }
58            const generatedName = factory.createUniqueName(`_${name}`, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel | GeneratedIdentifierFlags.AllowNameSubstitution);
59            const specifier = factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier(name), generatedName);
60            generatedName.generatedImportReference = specifier;
61            specifierSourceImports.set(name, specifier);
62            return generatedName;
63        }
64
65        /**
66         * Transform JSX-specific syntax in a SourceFile.
67         *
68         * @param node A SourceFile node.
69         */
70        function transformSourceFile(node: SourceFile) {
71            if (node.isDeclarationFile) {
72                return node;
73            }
74
75            currentSourceFile = node;
76            currentFileState = {};
77            currentFileState.importSpecifier = getJSXImplicitImportBase(compilerOptions, node);
78            let visited = visitEachChild(node, visitor, context);
79            addEmitHelpers(visited, context.readEmitHelpers());
80            let statements: readonly Statement[] = visited.statements;
81            if (currentFileState.filenameDeclaration) {
82                statements = insertStatementAfterCustomPrologue(statements.slice(), factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([currentFileState.filenameDeclaration], NodeFlags.Const)));
83            }
84            if (currentFileState.utilizedImplicitRuntimeImports) {
85                for (const [importSource, importSpecifiersMap] of arrayFrom(currentFileState.utilizedImplicitRuntimeImports.entries())) {
86                    if (isExternalModule(node)) {
87                        // Add `import` statement
88                        const importStatement = factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*typeOnly*/ false, /*name*/ undefined, factory.createNamedImports(arrayFrom(importSpecifiersMap.values()))), factory.createStringLiteral(importSource), /*assertClause*/ undefined);
89                        setParentRecursive(importStatement, /*incremental*/ false);
90                        statements = insertStatementAfterCustomPrologue(statements.slice(), importStatement);
91                    }
92                    else if (isExternalOrCommonJsModule(node)) {
93                        // Add `require` statement
94                        const requireStatement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([
95                            factory.createVariableDeclaration(
96                                factory.createObjectBindingPattern(map(arrayFrom(importSpecifiersMap.values()), s => factory.createBindingElement(/*dotdotdot*/ undefined, s.propertyName, s.name))),
97                                /*exclaimationToken*/ undefined,
98                                /*type*/ undefined,
99                                factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [factory.createStringLiteral(importSource)])
100                            )
101                        ], NodeFlags.Const));
102                        setParentRecursive(requireStatement, /*incremental*/ false);
103                        statements = insertStatementAfterCustomPrologue(statements.slice(), requireStatement);
104                    }
105                    else {
106                        // Do nothing (script file) - consider an error in the checker?
107                    }
108                }
109            }
110            if (statements !== visited.statements) {
111                visited = factory.updateSourceFile(visited, statements);
112            }
113            currentFileState = undefined!;
114            return visited;
115        }
116
117        function visitor(node: Node): VisitResult<Node> {
118            if (node.transformFlags & TransformFlags.ContainsJsx) {
119                return visitorWorker(node);
120            }
121            else {
122                return node;
123            }
124        }
125
126        function visitorWorker(node: Node): VisitResult<Node> {
127            switch (node.kind) {
128                case SyntaxKind.JsxElement:
129                    return visitJsxElement(node as JsxElement, /*isChild*/ false);
130
131                case SyntaxKind.JsxSelfClosingElement:
132                    return visitJsxSelfClosingElement(node as JsxSelfClosingElement, /*isChild*/ false);
133
134                case SyntaxKind.JsxFragment:
135                    return visitJsxFragment(node as JsxFragment, /*isChild*/ false);
136
137                case SyntaxKind.JsxExpression:
138                    return visitJsxExpression(node as JsxExpression);
139
140                default:
141                    return visitEachChild(node, visitor, context);
142            }
143        }
144
145        function transformJsxChildToExpression(node: JsxChild): Expression | undefined {
146            switch (node.kind) {
147                case SyntaxKind.JsxText:
148                    return visitJsxText(node);
149
150                case SyntaxKind.JsxExpression:
151                    return visitJsxExpression(node);
152
153                case SyntaxKind.JsxElement:
154                    return visitJsxElement(node, /*isChild*/ true);
155
156                case SyntaxKind.JsxSelfClosingElement:
157                    return visitJsxSelfClosingElement(node, /*isChild*/ true);
158
159                case SyntaxKind.JsxFragment:
160                    return visitJsxFragment(node, /*isChild*/ true);
161
162                default:
163                    return Debug.failBadSyntaxKind(node);
164            }
165        }
166
167        /**
168         * The react jsx/jsxs transform falls back to `createElement` when an explicit `key` argument comes after a spread
169         */
170        function hasKeyAfterPropsSpread(node: JsxOpeningLikeElement) {
171            let spread = false;
172            for (const elem of node.attributes.properties) {
173                if (isJsxSpreadAttribute(elem)) {
174                    spread = true;
175                }
176                else if (spread && isJsxAttribute(elem) && elem.name.escapedText === "key") {
177                    return true;
178                }
179            }
180            return false;
181        }
182
183        function shouldUseCreateElement(node: JsxOpeningLikeElement) {
184            return currentFileState.importSpecifier === undefined || hasKeyAfterPropsSpread(node);
185        }
186
187        function visitJsxElement(node: JsxElement, isChild: boolean) {
188            const tagTransform = shouldUseCreateElement(node.openingElement) ? visitJsxOpeningLikeElementCreateElement : visitJsxOpeningLikeElementJSX;
189            return tagTransform(node.openingElement, node.children, isChild, /*location*/ node);
190        }
191
192        function visitJsxSelfClosingElement(node: JsxSelfClosingElement, isChild: boolean) {
193            const tagTransform = shouldUseCreateElement(node) ? visitJsxOpeningLikeElementCreateElement : visitJsxOpeningLikeElementJSX;
194            return tagTransform(node, /*children*/ undefined, isChild, /*location*/ node);
195        }
196
197        function visitJsxFragment(node: JsxFragment, isChild: boolean) {
198            const tagTransform = currentFileState.importSpecifier === undefined ? visitJsxOpeningFragmentCreateElement : visitJsxOpeningFragmentJSX;
199            return tagTransform(node.openingFragment, node.children, isChild, /*location*/ node);
200        }
201
202        function convertJsxChildrenToChildrenPropObject(children: readonly JsxChild[]) {
203            const prop = convertJsxChildrenToChildrenPropAssignment(children);
204            return prop && factory.createObjectLiteralExpression([prop]);
205        }
206
207        function convertJsxChildrenToChildrenPropAssignment(children: readonly JsxChild[]) {
208            const nonWhitespaceChildren = getSemanticJsxChildren(children);
209            if (length(nonWhitespaceChildren) === 1 && !(nonWhitespaceChildren[0] as JsxExpression).dotDotDotToken) {
210                const result = transformJsxChildToExpression(nonWhitespaceChildren[0]);
211                return result && factory.createPropertyAssignment("children", result);
212            }
213            const result = mapDefined(children, transformJsxChildToExpression);
214            return length(result) ? factory.createPropertyAssignment("children", factory.createArrayLiteralExpression(result)) : undefined;
215        }
216
217        function visitJsxOpeningLikeElementJSX(node: JsxOpeningLikeElement, children: readonly JsxChild[] | undefined, isChild: boolean, location: TextRange) {
218            const tagName = getTagName(node);
219            const childrenProp = children && children.length ? convertJsxChildrenToChildrenPropAssignment(children) : undefined;
220            const keyAttr = find(node.attributes.properties, p => !!p.name && isIdentifier(p.name) && p.name.escapedText === "key") as JsxAttribute | undefined;
221            const attrs = keyAttr ? filter(node.attributes.properties, p => p !== keyAttr) : node.attributes.properties;
222            const objectProperties = length(attrs) ? transformJsxAttributesToObjectProps(attrs, childrenProp) :
223                factory.createObjectLiteralExpression(childrenProp ? [childrenProp] : emptyArray); // When there are no attributes, React wants {}
224            return visitJsxOpeningLikeElementOrFragmentJSX(
225                tagName,
226                objectProperties,
227                keyAttr,
228                children || emptyArray,
229                isChild,
230                location
231            );
232        }
233
234        function visitJsxOpeningLikeElementOrFragmentJSX(
235            tagName: Expression,
236            objectProperties: Expression,
237            keyAttr: JsxAttribute | undefined,
238            children: readonly JsxChild[],
239            isChild: boolean,
240            location: TextRange
241        ) {
242            const nonWhitespaceChildren = getSemanticJsxChildren(children);
243            const isStaticChildren =
244                length(nonWhitespaceChildren) > 1 || !!(nonWhitespaceChildren[0] as JsxExpression)?.dotDotDotToken;
245            const args: Expression[] = [tagName, objectProperties];
246            // function jsx(type, config, maybeKey) {}
247            // "maybeKey" is optional. It is acceptable to use "_jsx" without a third argument
248            if (keyAttr) {
249                args.push(transformJsxAttributeInitializer(keyAttr.initializer));
250            }
251            if (compilerOptions.jsx === JsxEmit.ReactJSXDev) {
252                const originalFile = getOriginalNode(currentSourceFile);
253                if (originalFile && isSourceFile(originalFile)) {
254                    // "maybeKey" has to be replaced with "void 0" to not break the jsxDEV signature
255                    if (keyAttr === undefined) {
256                        args.push(factory.createVoidZero());
257                    }
258                    // isStaticChildren development flag
259                    args.push(isStaticChildren ? factory.createTrue() : factory.createFalse());
260                    // __source development flag
261                    const lineCol = getLineAndCharacterOfPosition(originalFile, location.pos);
262                    args.push(factory.createObjectLiteralExpression([
263                        factory.createPropertyAssignment("fileName", getCurrentFileNameExpression()),
264                        factory.createPropertyAssignment("lineNumber", factory.createNumericLiteral(lineCol.line + 1)),
265                        factory.createPropertyAssignment("columnNumber", factory.createNumericLiteral(lineCol.character + 1))
266                    ]));
267                    // __self development flag
268                    args.push(factory.createThis());
269                }
270            }
271
272            const element = setTextRange(
273                factory.createCallExpression(getJsxFactoryCallee(isStaticChildren), /*typeArguments*/ undefined, args),
274                location
275            );
276
277            if (isChild) {
278                startOnNewLine(element);
279            }
280
281            return element;
282        }
283
284        function visitJsxOpeningLikeElementCreateElement(node: JsxOpeningLikeElement, children: readonly JsxChild[] | undefined, isChild: boolean, location: TextRange) {
285            const tagName = getTagName(node);
286            const attrs = node.attributes.properties;
287            const objectProperties = length(attrs) ? transformJsxAttributesToObjectProps(attrs) :
288                factory.createNull(); // When there are no attributes, React wants "null"
289
290            const callee = currentFileState.importSpecifier === undefined
291                ? createJsxFactoryExpression(
292                    factory,
293                    context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
294                    compilerOptions.reactNamespace!, // TODO: GH#18217
295                    node
296                )
297                : getImplicitImportForName("createElement");
298
299            const element = createExpressionForJsxElement(
300                factory,
301                callee,
302                tagName,
303                objectProperties,
304                mapDefined(children, transformJsxChildToExpression),
305                location
306            );
307
308            if (isChild) {
309                startOnNewLine(element);
310            }
311
312            return element;
313        }
314
315        function visitJsxOpeningFragmentJSX(_node: JsxOpeningFragment, children: readonly JsxChild[], isChild: boolean, location: TextRange) {
316            let childrenProps: Expression | undefined;
317            if (children && children.length) {
318                const result = convertJsxChildrenToChildrenPropObject(children);
319                if (result) {
320                    childrenProps = result;
321                }
322            }
323            return visitJsxOpeningLikeElementOrFragmentJSX(
324                getImplicitJsxFragmentReference(),
325                childrenProps || factory.createObjectLiteralExpression([]),
326                /*keyAttr*/ undefined,
327                children,
328                isChild,
329                location
330            );
331        }
332
333        function visitJsxOpeningFragmentCreateElement(node: JsxOpeningFragment, children: readonly JsxChild[], isChild: boolean, location: TextRange) {
334            const element = createExpressionForJsxFragment(
335                factory,
336                context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
337                context.getEmitResolver().getJsxFragmentFactoryEntity(currentSourceFile),
338                compilerOptions.reactNamespace!, // TODO: GH#18217
339                mapDefined(children, transformJsxChildToExpression),
340                node,
341                location
342            );
343
344            if (isChild) {
345                startOnNewLine(element);
346            }
347
348            return element;
349        }
350
351        function transformJsxSpreadAttributeToSpreadAssignment(node: JsxSpreadAttribute) {
352            return factory.createSpreadAssignment(visitNode(node.expression, visitor, isExpression));
353        }
354
355        function transformJsxAttributesToObjectProps(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) {
356            const target = getEmitScriptTarget(compilerOptions);
357            return target && target >= ScriptTarget.ES2018 ? factory.createObjectLiteralExpression(transformJsxAttributesToProps(attrs, children)) :
358                transformJsxAttributesToExpression(attrs, children);
359        }
360
361        function transformJsxAttributesToProps(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) {
362            const props = flatten<SpreadAssignment | PropertyAssignment>(spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) =>
363                map(attrs, attr => isSpread ? transformJsxSpreadAttributeToSpreadAssignment(attr as JsxSpreadAttribute) : transformJsxAttributeToObjectLiteralElement(attr as JsxAttribute))));
364            if (children) {
365                props.push(children);
366            }
367            return props;
368        }
369
370        function transformJsxAttributesToExpression(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) {
371            // Map spans of JsxAttribute nodes into object literals and spans
372            // of JsxSpreadAttribute nodes into expressions.
373            const expressions = flatten(
374                spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
375                    ? map(attrs, transformJsxSpreadAttributeToExpression)
376                    : factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement))
377                )
378            );
379
380            if (isJsxSpreadAttribute(attrs[0])) {
381                // We must always emit at least one object literal before a spread
382                // argument.factory.createObjectLiteral
383                expressions.unshift(factory.createObjectLiteralExpression());
384            }
385
386            if (children) {
387                expressions.push(factory.createObjectLiteralExpression([children]));
388            }
389
390            return singleOrUndefined(expressions) || emitHelpers().createAssignHelper(expressions);
391        }
392
393        function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {
394            return visitNode(node.expression, visitor, isExpression);
395        }
396
397        function transformJsxAttributeToObjectLiteralElement(node: JsxAttribute) {
398            const name = getAttributeName(node);
399            const expression = transformJsxAttributeInitializer(node.initializer);
400            return factory.createPropertyAssignment(name, expression);
401        }
402
403        function transformJsxAttributeInitializer(node: JsxAttributeValue | undefined): Expression {
404            if (node === undefined) {
405                return factory.createTrue();
406            }
407            if (node.kind === SyntaxKind.StringLiteral) {
408                // Always recreate the literal to escape any escape sequences or newlines which may be in the original jsx string and which
409                // Need to be escaped to be handled correctly in a normal string
410                const singleQuote = node.singleQuote !== undefined ? node.singleQuote : !isStringDoubleQuoted(node, currentSourceFile);
411                const literal = factory.createStringLiteral(tryDecodeEntities(node.text) || node.text, singleQuote);
412                return setTextRange(literal, node);
413            }
414            if (node.kind === SyntaxKind.JsxExpression) {
415                if (node.expression === undefined) {
416                    return factory.createTrue();
417                }
418                return visitNode(node.expression, visitor, isExpression);
419            }
420            if (isJsxElement(node)) {
421                return visitJsxElement(node, /*isChild*/ false);
422            }
423            if (isJsxSelfClosingElement(node)) {
424                return visitJsxSelfClosingElement(node, /*isChild*/ false);
425            }
426            if (isJsxFragment(node)) {
427                return visitJsxFragment(node, /*isChild*/ false);
428            }
429            return Debug.failBadSyntaxKind(node);
430        }
431
432        function visitJsxText(node: JsxText): StringLiteral | undefined {
433            const fixed = fixupWhitespaceAndDecodeEntities(node.text);
434            return fixed === undefined ? undefined : factory.createStringLiteral(fixed);
435        }
436
437        /**
438         * JSX trims whitespace at the end and beginning of lines, except that the
439         * start/end of a tag is considered a start/end of a line only if that line is
440         * on the same line as the closing tag. See examples in
441         * tests/cases/conformance/jsx/tsxReactEmitWhitespace.tsx
442         * See also https://www.w3.org/TR/html4/struct/text.html#h-9.1 and https://www.w3.org/TR/CSS2/text.html#white-space-model
443         *
444         * An equivalent algorithm would be:
445         * - If there is only one line, return it.
446         * - If there is only whitespace (but multiple lines), return `undefined`.
447         * - Split the text into lines.
448         * - 'trimRight' the first line, 'trimLeft' the last line, 'trim' middle lines.
449         * - Decode entities on each line (individually).
450         * - Remove empty lines and join the rest with " ".
451         */
452        function fixupWhitespaceAndDecodeEntities(text: string): string | undefined {
453            let acc: string | undefined;
454            // First non-whitespace character on this line.
455            let firstNonWhitespace = 0;
456            // Last non-whitespace character on this line.
457            let lastNonWhitespace = -1;
458            // These initial values are special because the first line is:
459            // firstNonWhitespace = 0 to indicate that we want leading whitsepace,
460            // but lastNonWhitespace = -1 as a special flag to indicate that we *don't* include the line if it's all whitespace.
461
462            for (let i = 0; i < text.length; i++) {
463                const c = text.charCodeAt(i);
464                if (isLineBreak(c)) {
465                    // If we've seen any non-whitespace characters on this line, add the 'trim' of the line.
466                    // (lastNonWhitespace === -1 is a special flag to detect whether the first line is all whitespace.)
467                    if (firstNonWhitespace !== -1 && lastNonWhitespace !== -1) {
468                        acc = addLineOfJsxText(acc, text.substr(firstNonWhitespace, lastNonWhitespace - firstNonWhitespace + 1));
469                    }
470
471                    // Reset firstNonWhitespace for the next line.
472                    // Don't bother to reset lastNonWhitespace because we ignore it if firstNonWhitespace = -1.
473                    firstNonWhitespace = -1;
474                }
475                else if (!isWhiteSpaceSingleLine(c)) {
476                    lastNonWhitespace = i;
477                    if (firstNonWhitespace === -1) {
478                        firstNonWhitespace = i;
479                    }
480                }
481            }
482
483            return firstNonWhitespace !== -1
484                // Last line had a non-whitespace character. Emit the 'trimLeft', meaning keep trailing whitespace.
485                ? addLineOfJsxText(acc, text.substr(firstNonWhitespace))
486                // Last line was all whitespace, so ignore it
487                : acc;
488        }
489
490        function addLineOfJsxText(acc: string | undefined, trimmedLine: string): string {
491            // We do not escape the string here as that is handled by the printer
492            // when it emits the literal. We do, however, need to decode JSX entities.
493            const decoded = decodeEntities(trimmedLine);
494            return acc === undefined ? decoded : acc + " " + decoded;
495        }
496
497        /**
498         * Replace entities like "&nbsp;", "&#123;", and "&#xDEADBEEF;" with the characters they encode.
499         * See https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
500         */
501        function decodeEntities(text: string): string {
502            return text.replace(/&((#((\d+)|x([\da-fA-F]+)))|(\w+));/g, (match, _all, _number, _digits, decimal, hex, word) => {
503                if (decimal) {
504                    return utf16EncodeAsString(parseInt(decimal, 10));
505                }
506                else if (hex) {
507                    return utf16EncodeAsString(parseInt(hex, 16));
508                }
509                else {
510                    const ch = entities.get(word);
511                    // If this is not a valid entity, then just use `match` (replace it with itself, i.e. don't replace)
512                    return ch ? utf16EncodeAsString(ch) : match;
513                }
514            });
515        }
516
517        /** Like `decodeEntities` but returns `undefined` if there were no entities to decode. */
518        function tryDecodeEntities(text: string): string | undefined {
519            const decoded = decodeEntities(text);
520            return decoded === text ? undefined : decoded;
521        }
522
523        function getTagName(node: JsxElement | JsxOpeningLikeElement): Expression {
524            if (node.kind === SyntaxKind.JsxElement) {
525                return getTagName(node.openingElement);
526            }
527            else {
528                const name = node.tagName;
529                if (isIdentifier(name) && isIntrinsicJsxName(name.escapedText)) {
530                    return factory.createStringLiteral(idText(name));
531                }
532                else {
533                    return createExpressionFromEntityName(factory, name);
534                }
535            }
536        }
537
538        /**
539         * Emit an attribute name, which is quoted if it needs to be quoted. Because
540         * these emit into an object literal property name, we don't need to be worried
541         * about keywords, just non-identifier characters
542         */
543        function getAttributeName(node: JsxAttribute): StringLiteral | Identifier {
544            const name = node.name;
545            const text = idText(name);
546            if (/^[A-Za-z_]\w*$/.test(text)) {
547                return name;
548            }
549            else {
550                return factory.createStringLiteral(text);
551            }
552        }
553
554        function visitJsxExpression(node: JsxExpression) {
555            const expression = visitNode(node.expression, visitor, isExpression);
556            return node.dotDotDotToken ? factory.createSpreadElement(expression!) : expression;
557        }
558    }
559
560    const entities = new Map(getEntries({
561        quot: 0x0022,
562        amp: 0x0026,
563        apos: 0x0027,
564        lt: 0x003C,
565        gt: 0x003E,
566        nbsp: 0x00A0,
567        iexcl: 0x00A1,
568        cent: 0x00A2,
569        pound: 0x00A3,
570        curren: 0x00A4,
571        yen: 0x00A5,
572        brvbar: 0x00A6,
573        sect: 0x00A7,
574        uml: 0x00A8,
575        copy: 0x00A9,
576        ordf: 0x00AA,
577        laquo: 0x00AB,
578        not: 0x00AC,
579        shy: 0x00AD,
580        reg: 0x00AE,
581        macr: 0x00AF,
582        deg: 0x00B0,
583        plusmn: 0x00B1,
584        sup2: 0x00B2,
585        sup3: 0x00B3,
586        acute: 0x00B4,
587        micro: 0x00B5,
588        para: 0x00B6,
589        middot: 0x00B7,
590        cedil: 0x00B8,
591        sup1: 0x00B9,
592        ordm: 0x00BA,
593        raquo: 0x00BB,
594        frac14: 0x00BC,
595        frac12: 0x00BD,
596        frac34: 0x00BE,
597        iquest: 0x00BF,
598        Agrave: 0x00C0,
599        Aacute: 0x00C1,
600        Acirc: 0x00C2,
601        Atilde: 0x00C3,
602        Auml: 0x00C4,
603        Aring: 0x00C5,
604        AElig: 0x00C6,
605        Ccedil: 0x00C7,
606        Egrave: 0x00C8,
607        Eacute: 0x00C9,
608        Ecirc: 0x00CA,
609        Euml: 0x00CB,
610        Igrave: 0x00CC,
611        Iacute: 0x00CD,
612        Icirc: 0x00CE,
613        Iuml: 0x00CF,
614        ETH: 0x00D0,
615        Ntilde: 0x00D1,
616        Ograve: 0x00D2,
617        Oacute: 0x00D3,
618        Ocirc: 0x00D4,
619        Otilde: 0x00D5,
620        Ouml: 0x00D6,
621        times: 0x00D7,
622        Oslash: 0x00D8,
623        Ugrave: 0x00D9,
624        Uacute: 0x00DA,
625        Ucirc: 0x00DB,
626        Uuml: 0x00DC,
627        Yacute: 0x00DD,
628        THORN: 0x00DE,
629        szlig: 0x00DF,
630        agrave: 0x00E0,
631        aacute: 0x00E1,
632        acirc: 0x00E2,
633        atilde: 0x00E3,
634        auml: 0x00E4,
635        aring: 0x00E5,
636        aelig: 0x00E6,
637        ccedil: 0x00E7,
638        egrave: 0x00E8,
639        eacute: 0x00E9,
640        ecirc: 0x00EA,
641        euml: 0x00EB,
642        igrave: 0x00EC,
643        iacute: 0x00ED,
644        icirc: 0x00EE,
645        iuml: 0x00EF,
646        eth: 0x00F0,
647        ntilde: 0x00F1,
648        ograve: 0x00F2,
649        oacute: 0x00F3,
650        ocirc: 0x00F4,
651        otilde: 0x00F5,
652        ouml: 0x00F6,
653        divide: 0x00F7,
654        oslash: 0x00F8,
655        ugrave: 0x00F9,
656        uacute: 0x00FA,
657        ucirc: 0x00FB,
658        uuml: 0x00FC,
659        yacute: 0x00FD,
660        thorn: 0x00FE,
661        yuml: 0x00FF,
662        OElig: 0x0152,
663        oelig: 0x0153,
664        Scaron: 0x0160,
665        scaron: 0x0161,
666        Yuml: 0x0178,
667        fnof: 0x0192,
668        circ: 0x02C6,
669        tilde: 0x02DC,
670        Alpha: 0x0391,
671        Beta: 0x0392,
672        Gamma: 0x0393,
673        Delta: 0x0394,
674        Epsilon: 0x0395,
675        Zeta: 0x0396,
676        Eta: 0x0397,
677        Theta: 0x0398,
678        Iota: 0x0399,
679        Kappa: 0x039A,
680        Lambda: 0x039B,
681        Mu: 0x039C,
682        Nu: 0x039D,
683        Xi: 0x039E,
684        Omicron: 0x039F,
685        Pi: 0x03A0,
686        Rho: 0x03A1,
687        Sigma: 0x03A3,
688        Tau: 0x03A4,
689        Upsilon: 0x03A5,
690        Phi: 0x03A6,
691        Chi: 0x03A7,
692        Psi: 0x03A8,
693        Omega: 0x03A9,
694        alpha: 0x03B1,
695        beta: 0x03B2,
696        gamma: 0x03B3,
697        delta: 0x03B4,
698        epsilon: 0x03B5,
699        zeta: 0x03B6,
700        eta: 0x03B7,
701        theta: 0x03B8,
702        iota: 0x03B9,
703        kappa: 0x03BA,
704        lambda: 0x03BB,
705        mu: 0x03BC,
706        nu: 0x03BD,
707        xi: 0x03BE,
708        omicron: 0x03BF,
709        pi: 0x03C0,
710        rho: 0x03C1,
711        sigmaf: 0x03C2,
712        sigma: 0x03C3,
713        tau: 0x03C4,
714        upsilon: 0x03C5,
715        phi: 0x03C6,
716        chi: 0x03C7,
717        psi: 0x03C8,
718        omega: 0x03C9,
719        thetasym: 0x03D1,
720        upsih: 0x03D2,
721        piv: 0x03D6,
722        ensp: 0x2002,
723        emsp: 0x2003,
724        thinsp: 0x2009,
725        zwnj: 0x200C,
726        zwj: 0x200D,
727        lrm: 0x200E,
728        rlm: 0x200F,
729        ndash: 0x2013,
730        mdash: 0x2014,
731        lsquo: 0x2018,
732        rsquo: 0x2019,
733        sbquo: 0x201A,
734        ldquo: 0x201C,
735        rdquo: 0x201D,
736        bdquo: 0x201E,
737        dagger: 0x2020,
738        Dagger: 0x2021,
739        bull: 0x2022,
740        hellip: 0x2026,
741        permil: 0x2030,
742        prime: 0x2032,
743        Prime: 0x2033,
744        lsaquo: 0x2039,
745        rsaquo: 0x203A,
746        oline: 0x203E,
747        frasl: 0x2044,
748        euro: 0x20AC,
749        image: 0x2111,
750        weierp: 0x2118,
751        real: 0x211C,
752        trade: 0x2122,
753        alefsym: 0x2135,
754        larr: 0x2190,
755        uarr: 0x2191,
756        rarr: 0x2192,
757        darr: 0x2193,
758        harr: 0x2194,
759        crarr: 0x21B5,
760        lArr: 0x21D0,
761        uArr: 0x21D1,
762        rArr: 0x21D2,
763        dArr: 0x21D3,
764        hArr: 0x21D4,
765        forall: 0x2200,
766        part: 0x2202,
767        exist: 0x2203,
768        empty: 0x2205,
769        nabla: 0x2207,
770        isin: 0x2208,
771        notin: 0x2209,
772        ni: 0x220B,
773        prod: 0x220F,
774        sum: 0x2211,
775        minus: 0x2212,
776        lowast: 0x2217,
777        radic: 0x221A,
778        prop: 0x221D,
779        infin: 0x221E,
780        ang: 0x2220,
781        and: 0x2227,
782        or: 0x2228,
783        cap: 0x2229,
784        cup: 0x222A,
785        int: 0x222B,
786        there4: 0x2234,
787        sim: 0x223C,
788        cong: 0x2245,
789        asymp: 0x2248,
790        ne: 0x2260,
791        equiv: 0x2261,
792        le: 0x2264,
793        ge: 0x2265,
794        sub: 0x2282,
795        sup: 0x2283,
796        nsub: 0x2284,
797        sube: 0x2286,
798        supe: 0x2287,
799        oplus: 0x2295,
800        otimes: 0x2297,
801        perp: 0x22A5,
802        sdot: 0x22C5,
803        lceil: 0x2308,
804        rceil: 0x2309,
805        lfloor: 0x230A,
806        rfloor: 0x230B,
807        lang: 0x2329,
808        rang: 0x232A,
809        loz: 0x25CA,
810        spades: 0x2660,
811        clubs: 0x2663,
812        hearts: 0x2665,
813        diams: 0x2666
814    }));
815}
816