com.google.javascript.jscomp.Es6TemplateLiterals.java Source code

Java tutorial

Introduction

Here is the source code for com.google.javascript.jscomp.Es6TemplateLiterals.java

Source

/*
 * Copyright 2014 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.javascript.jscomp;

import com.google.common.base.Preconditions;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;

/**
 * Helper class for transpiling ES6 template literals.
 *
 * @author moz@google.com (Michael Zhou)
 */
class Es6TemplateLiterals {
    private static final String TEMPLATELIT_VAR = "$jscomp$templatelit$";

    /**
     * Converts `${a} b ${c} d ${e}` to (a + " b " + c + " d " + e)
     *
     * @param n A TEMPLATELIT node that is not prefixed with a tag
     */
    static void visitTemplateLiteral(NodeTraversal t, Node n) {
        int length = n.getChildCount();
        if (length == 0) {
            n.getParent().replaceChild(n, IR.string("\"\""));
        } else {
            Node first = n.removeFirstChild();
            Preconditions.checkState(first.isString());
            if (length == 1) {
                n.getParent().replaceChild(n, first);
            } else {
                // Add the first string with the first substitution expression
                Node add = IR.add(first, n.removeFirstChild().removeFirstChild());
                // Process the rest of the template literal
                for (int i = 2; i < length; i++) {
                    Node child = n.removeFirstChild();
                    if (child.isString()) {
                        if (child.getString().isEmpty()) {
                            continue;
                        } else if (i == 2 && first.getString().isEmpty()) {
                            // So that `${hello} world` gets translated into (hello + " world")
                            // instead of ("" + hello + " world").
                            add = add.getChildAtIndex(1).detachFromParent();
                        }
                    }
                    add = IR.add(add, child.isString() ? child : child.removeFirstChild());
                }
                n.getParent().replaceChild(n, add.useSourceInfoIfMissingFromForTree(n));
            }
        }
        t.getCompiler().reportCodeChange();
    }

    /**
     * Converts tag`a\tb${bar}` to:
     *   // A global (module) scoped variable
     *   var $jscomp$templatelit$0 = ["a\tb"];    // cooked string array
     *   $jscomp$templatelit$0["raw"] = ["a\\tb"]; // raw string array
     *   ...
     *   // A call to the tagging function
     *   tag($jscomp$templatelit$0, bar);
     *
     *   See template_literal_test.js for more examples.
     *
     * @param n A TAGGED_TEMPLATELIT node
     */
    static void visitTaggedTemplateLiteral(NodeTraversal t, Node n) {
        Node templateLit = n.getLastChild();
        // Prepare the raw and cooked string arrays.
        Node raw = createRawStringArray(templateLit);
        Node cooked = createCookedStringArray(templateLit);

        // Create a variable representing the template literal.
        Node callsiteId = IR.name(TEMPLATELIT_VAR + t.getCompiler().getUniqueNameIdSupplier().get());
        Node var = IR.var(callsiteId, cooked).useSourceInfoIfMissingFromForTree(n);
        Node script = NodeUtil.getEnclosingType(n, Token.SCRIPT);
        script.addChildrenToFront(var);

        // Define the "raw" property on the introduced variable.
        Node defineRaw = IR.exprResult(IR.assign(IR.getelem(callsiteId.cloneNode(), IR.string("raw")), raw))
                .useSourceInfoIfMissingFromForTree(n);
        script.addChildAfter(defineRaw, var);

        // Generate the call expression.
        Node call = IR.call(n.removeFirstChild(), callsiteId.cloneNode());
        for (Node child = templateLit.getFirstChild(); child != null; child = child.getNext()) {
            if (!child.isString()) {
                call.addChildToBack(child.removeFirstChild());
            }
        }
        call.useSourceInfoIfMissingFromForTree(templateLit);
        call.putBooleanProp(Node.FREE_CALL, !call.getFirstChild().isGetProp());
        n.getParent().replaceChild(n, call);
        t.getCompiler().reportCodeChange();
    }

    private static Node createRawStringArray(Node n) {
        Node array = IR.arraylit();
        for (Node child = n.getFirstChild(); child != null; child = child.getNext()) {
            if (child.isString()) {
                array.addChildToBack(IR.string((String) child.getProp(Node.RAW_STRING_VALUE)));
            }
        }
        return array;
    }

    private static Node createCookedStringArray(Node n) {
        Node array = IR.arraylit();
        for (Node child = n.getFirstChild(); child != null; child = child.getNext()) {
            if (child.isString()) {
                Node string = IR.string(cookString((String) child.getProp(Node.RAW_STRING_VALUE)));
                string.putBooleanProp(Node.COOKED_STRING, true);
                array.addChildToBack(string);
            }
        }
        return array;
    }

    /**
     * Takes a raw string and returns a string that is suitable for the cooked
     * value (the Template Value or TV as described in the specs). This involves
     * removing line continuations.
     */
    private static String cookString(String s) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length();) {
            char c = s.charAt(i++);
            switch (c) {
            case '\\':
                char c2 = s.charAt(i++);
                switch (c2) {
                // Strip line continuation.
                case '\n':
                case '\u2028':
                case '\u2029':
                    break;
                case '\r':
                    // \ \r \n should be stripped as one
                    if (s.charAt(i + 1) == '\n') {
                        i++;
                    }
                    break;

                default:
                    sb.append(c);
                    sb.append(c2);
                }
                break;

            // Whitespace
            case '\n':
                sb.append("\\n");
                break;
            // <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF>
            // for both TV and TRV.
            case '\r':
                if (s.charAt(i) == '\n') {
                    i++;
                }
                sb.append("\\n");
                break;
            case '\u2028':
                sb.append("\\u2028");
                break;
            case '\u2029':
                sb.append("\\u2029");
                break;

            default:
                sb.append(c);
            }
        }
        return sb.toString();
    }
}