All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.Es6TemplateLiterals Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * 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 static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.AstFactory.type;

import com.google.javascript.jscomp.colors.Color;
import com.google.javascript.jscomp.colors.StandardColors;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticScope;

/**
 * Helper class for transpiling ES6 template literals.
 */
class Es6TemplateLiterals {
  private static final String TEMPLATELIT_VAR = "$jscomp$templatelit$";

  private final AstFactory astFactory;
  private final AbstractCompiler compiler;
  private final StaticScope namespace;

  Es6TemplateLiterals(AbstractCompiler compiler) {
    this.compiler = compiler;
    this.astFactory = compiler.createAstFactory();
    this.namespace = this.compiler.getTranspilationNamespace();
  }

  /**
   * Converts `${a} b ${c} d ${e}` to (a + " b " + c + " d " + e)
   *
   * @param n A TEMPLATELIT node that is not prefixed with a tag
   */
  void visitTemplateLiteral(NodeTraversal t, Node n) {
    int length = n.getChildCount();
    if (length == 0) {
      n.replaceWith(astFactory.createString("\"\""));
    } else {
      Node first = n.removeFirstChild();
      checkState(first.isTemplateLitString() && first.getCookedString() != null);
      Node firstStr = astFactory.createString(first.getCookedString());
      if (length == 1) {
        n.replaceWith(firstStr);
      } else {
        // Add the first string with the first substitution expression
        Node add = astFactory.createAdd(firstStr, n.removeFirstChild().removeFirstChild());
        // Process the rest of the template literal
        for (int i = 2; i < length; i++) {
          Node child = n.removeFirstChild();
          if (child.isTemplateLitString()) {
            checkState(child.getCookedString() != null);
            if (child.getCookedString().isEmpty()) {
              continue;
            } else if (i == 2 && first.getCookedString().isEmpty()) {
              // So that `${hello} world` gets translated into (hello + " world")
              // instead of ("" + hello + " world").
              add = add.getSecondChild().detach();
            }
          }
          add =
              astFactory.createAdd(
                  add,
                  child.isTemplateLitString()
                      ? astFactory.createString(child.getCookedString())
                      : child.removeFirstChild());
        }
        n.replaceWith(add.srcrefTreeIfMissing(n));
      }
    }
    t.reportCodeChange();
  }

  /**
   * Converts a tagged template into a call to the tag.
   *
   * 

If the cooked and raw strings of the template literal are same, this will create a call to * createtemplatetagfirstarg that simply calls slice() on the cooked array at runtime to make the * raw array a copy of the cooked array, and returns the cooked array. For example, tag`ab${bar}` * will change to: * *

* // A call to the tagging function: * tag($jscomp$templatelit$createTemplateTagFirstArg(['ab'], bar); * * *

If the cooked and raw strings of the template literal are not same, this will construct the * raw strings array, and call {@code createtemplatetagfirstargwithraw} with it, which assigns the * raw strings array to the property 'raw' of the cooked array at runtime, and returns the cooked * array. For example, tag`a\tb${bar}` will change to: * *

* // A call to the tagging function: * tag($jscomp$templatelit$createTemplateTagFirstArgWithRaw(['a\tb'] /, ['a\\tb']), bar); * * *

See template_literal_test.js for more examples. * * @param n A TAGGED_TEMPLATELIT node */ void visitTaggedTemplateLiteral(NodeTraversal t, Node n, Node insertBefore) { Node templateLit = n.getLastChild(); Node siteObject = createCookedStringArray(templateLit); // Node holding the function call to the runtime injected function Node callTemplateTagArgCreator; if (cookedAndRawStringsSame(templateLit)) { // The cooked and raw versions of the array are the same, so just call slice() on the // cooked array at runtime to make the raw array a copy of the cooked array. callTemplateTagArgCreator = astFactory.createCall( astFactory.createQName(this.namespace, "$jscomp.createTemplateTagFirstArg"), type(siteObject), siteObject.cloneTree()); } else { // The raw string array is different, so we need to construct it. Node raw = createRawStringArray(templateLit); callTemplateTagArgCreator = astFactory.createCall( astFactory.createQName(this.namespace, "$jscomp.createTemplateTagFirstArgWithRaw"), type(siteObject), siteObject.cloneTree(), raw); } JSDocInfo.Builder jsDocInfoBuilder = JSDocInfo.builder(); jsDocInfoBuilder.recordNoInline(); JSDocInfo info = jsDocInfoBuilder.build(); CompilerInput input = t.getInput(); String uniqueId = compiler.getUniqueIdSupplier().getUniqueId(input); // var tagFnFirstArg = $jscomp.createTemplateTagFirstArg... Node tagFnFirstArgDeclaration = astFactory .createSingleVarNameDeclaration(TEMPLATELIT_VAR + uniqueId, callTemplateTagArgCreator) .setJSDocInfo(info) .srcrefTreeIfMissing(n); // For the first script, insertion point is right after the runtime injected function definition // as injecting to the top of the script causes runtime errors // https://github.com/google/closure-compiler/issues/3589. For any subsequent script(s), the // call is injected to the top of that script. tagFnFirstArgDeclaration.insertBefore(insertBefore); t.reportCodeChange(tagFnFirstArgDeclaration); // Generate the call expression. Node tagFnFirstArg = tagFnFirstArgDeclaration.getFirstChild().cloneNode(); Node call = astFactory.createCall(n.removeFirstChild(), type(n), tagFnFirstArg); for (Node child = templateLit.getFirstChild(); child != null; child = child.getNext()) { if (!child.isTemplateLitString()) { call.addChildToBack(child.removeFirstChild()); } } call.srcrefTreeIfMissing(templateLit); call.putBooleanProp(Node.FREE_CALL, !call.getFirstChild().isGetProp()); n.replaceWith(call); t.reportCodeChange(); } private Node createRawStringArray(Node n) { Node array = astFactory.createArraylit(); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (child.isTemplateLitString()) { array.addChildToBack(astFactory.createString(child.getRawString())); } } return array; } private Node createCookedStringArray(Node n) { Color templateArrayType = astFactory.isAddingColors() ? compiler.getColorRegistry().get(StandardColors.I_TEMPLATE_ARRAY_ID) : null; Node array = astFactory .createArraylit() .setColor(templateArrayType); // tighten the type from Array to ITemplateArray for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (child.isTemplateLitString()) { if (child.getCookedString() != null) { array.addChildToBack(astFactory.createString(child.getCookedString())); } else { // undefined cooked string due to exception in template escapes array.addChildToBack(astFactory.createVoid(astFactory.createNumber(0))); } } } return array; } private static boolean cookedAndRawStringsSame(Node n) { for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!child.isTemplateLitString()) { continue; } // getCookedString() returns null when the template literal has an illegal escape sequence. if (child.getCookedString() == null || !child.getCookedString().equals(child.getRawString())) { return false; } } return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy