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