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 " ", "{", and "�" 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