1import { 2 Bundle, 3 chainBundle, EmitHint, Expression, getOriginalNodeId, Identifier, idText, isIdentifier, isPrivateIdentifier, 4 isPropertyAccessExpression, isPropertyAssignment, JsxClosingElement, JsxEmit, JsxOpeningElement, 5 JsxSelfClosingElement, Node, nodeIsSynthesized, PropertyAccessExpression, PropertyAssignment, setTextRange, 6 SourceFile, stringToToken, SyntaxKind, TransformationContext, 7} from "../_namespaces/ts"; 8 9/** 10 * Transforms ES5 syntax into ES3 syntax. 11 * 12 * @param context Context and state information for the transformation. 13 * 14 * @internal 15 */ 16export function transformES5(context: TransformationContext): (x: SourceFile | Bundle) => SourceFile | Bundle { 17 const { factory } = context; 18 const compilerOptions = context.getCompilerOptions(); 19 20 // enable emit notification only if using --jsx preserve or react-native 21 let previousOnEmitNode: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; 22 let noSubstitution: boolean[]; 23 if (compilerOptions.jsx === JsxEmit.Preserve || compilerOptions.jsx === JsxEmit.ReactNative) { 24 previousOnEmitNode = context.onEmitNode; 25 context.onEmitNode = onEmitNode; 26 context.enableEmitNotification(SyntaxKind.JsxOpeningElement); 27 context.enableEmitNotification(SyntaxKind.JsxClosingElement); 28 context.enableEmitNotification(SyntaxKind.JsxSelfClosingElement); 29 noSubstitution = []; 30 } 31 32 const previousOnSubstituteNode = context.onSubstituteNode; 33 context.onSubstituteNode = onSubstituteNode; 34 context.enableSubstitution(SyntaxKind.PropertyAccessExpression); 35 context.enableSubstitution(SyntaxKind.PropertyAssignment); 36 return chainBundle(context, transformSourceFile); 37 38 /** 39 * Transforms an ES5 source file to ES3. 40 * 41 * @param node A SourceFile 42 */ 43 function transformSourceFile(node: SourceFile) { 44 return node; 45 } 46 47 /** 48 * Called by the printer just before a node is printed. 49 * 50 * @param hint A hint as to the intended usage of the node. 51 * @param node The node to emit. 52 * @param emitCallback A callback used to emit the node. 53 */ 54 function onEmitNode(hint: EmitHint, node: Node, emitCallback: (emitContext: EmitHint, node: Node) => void) { 55 switch (node.kind) { 56 case SyntaxKind.JsxOpeningElement: 57 case SyntaxKind.JsxClosingElement: 58 case SyntaxKind.JsxSelfClosingElement: 59 const tagName = (node as JsxOpeningElement | JsxClosingElement | JsxSelfClosingElement).tagName; 60 noSubstitution[getOriginalNodeId(tagName)] = true; 61 break; 62 } 63 64 previousOnEmitNode(hint, node, emitCallback); 65 } 66 67 /** 68 * Hooks node substitutions. 69 * 70 * @param hint A hint as to the intended usage of the node. 71 * @param node The node to substitute. 72 */ 73 function onSubstituteNode(hint: EmitHint, node: Node) { 74 if (node.id && noSubstitution && noSubstitution[node.id]) { 75 return previousOnSubstituteNode(hint, node); 76 } 77 78 node = previousOnSubstituteNode(hint, node); 79 if (isPropertyAccessExpression(node)) { 80 return substitutePropertyAccessExpression(node); 81 } 82 else if (isPropertyAssignment(node)) { 83 return substitutePropertyAssignment(node); 84 } 85 return node; 86 } 87 88 /** 89 * Substitutes a PropertyAccessExpression whose name is a reserved word. 90 * 91 * @param node A PropertyAccessExpression 92 */ 93 function substitutePropertyAccessExpression(node: PropertyAccessExpression): Expression { 94 if (isPrivateIdentifier(node.name)) { 95 return node; 96 } 97 const literalName = trySubstituteReservedName(node.name); 98 if (literalName) { 99 return setTextRange(factory.createElementAccessExpression(node.expression, literalName), node); 100 } 101 return node; 102 } 103 104 /** 105 * Substitutes a PropertyAssignment whose name is a reserved word. 106 * 107 * @param node A PropertyAssignment 108 */ 109 function substitutePropertyAssignment(node: PropertyAssignment): PropertyAssignment { 110 const literalName = isIdentifier(node.name) && trySubstituteReservedName(node.name); 111 if (literalName) { 112 return factory.updatePropertyAssignment(node, literalName, node.initializer); 113 } 114 return node; 115 } 116 117 /** 118 * If an identifier name is a reserved word, returns a string literal for the name. 119 * 120 * @param name An Identifier 121 */ 122 function trySubstituteReservedName(name: Identifier) { 123 const token = name.originalKeywordKind || (nodeIsSynthesized(name) ? stringToToken(idText(name)) : undefined); 124 if (token !== undefined && token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord) { 125 return setTextRange(factory.createStringLiteralFromNode(name), name); 126 } 127 return undefined; 128 } 129} 130