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