• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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