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

com.google.javascript.jscomp.CreateSyntheticBlocks 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 2009 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.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

/**
 * Creates synthetic blocks to simplify preventing optimizations from moving code past "markers" in
 * the source.
 *
 * 

This supports a serving infrastructure that selectively removes parts that the code, that are * tagged using the start/end section markers. This removal happens at serving time not compilation * time. The conventional markers are "_START_TEMPLATE_SECTION" and "_END_TEMPLATE_SECTION" * respectively. */ class CreateSyntheticBlocks extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType UNMATCHED_START_MARKER = DiagnosticType.error("JSC_UNMATCHED_START_MARKER", "Unmatched {0}"); static final DiagnosticType UNMATCHED_END_MARKER = DiagnosticType.error("JSC_UNMATCHED_END_MARKER", "Unmatched {1} - {0} not in the same block"); static final DiagnosticType INVALID_MARKER_USAGE = DiagnosticType.error( "JSC_INVALID_MARKER_USAGE", "Marker {0} can only be used in a simple call expression"); private final AbstractCompiler compiler; /** Name of the start marker. */ private final String startMarkerName; /** Name of the end marker. */ private final String endMarkerName; /** Markers can be nested. */ private final Deque markerStack = new ArrayDeque<>(); private final List validMarkers = new ArrayList<>(); private static class Marker { final Node startMarker; final Node endMarker; public Marker(Node startMarker, Node endMarker) { this.startMarker = startMarker; this.endMarker = endMarker; } } public CreateSyntheticBlocks( AbstractCompiler compiler, String startMarkerName, String endMarkerName) { this.compiler = compiler; this.startMarkerName = startMarkerName; this.endMarkerName = endMarkerName; } @Override public void process(Node externs, Node root) { // Find and validate the markers. NodeTraversal.traverse(compiler, root, this); // Complain about any unmatched markers. for (Node node : markerStack) { compiler.report(JSError.make(node, UNMATCHED_START_MARKER, startMarkerName)); } // Add the block for the valid marker sets. for (Marker marker : validMarkers) { addBlocks(marker); } } /** * Rewrites the function declaration from: function x() {} FUNCTION NAME x PARAM_LIST BLOCK to: * var x = function() {}; VAR NAME x FUNCTION NAME (w/ empty string) PARAM_LIST BLOCK */ private void rewriteFunctionDeclaration(Node n) { // Prepare a variable for the function. Node oldNameNode = n.getFirstChild(); Node fnNameNode = oldNameNode.cloneNode(); Node var = IR.var(fnNameNode).srcref(n); // Prepare the function. oldNameNode.setString(""); compiler.reportChangeToEnclosingScope(oldNameNode); // Move the function to the front of the parent. Node parent = n.getParent(); n.detach(); parent.addChildToFront(var); compiler.reportChangeToEnclosingScope(var); fnNameNode.addChildToFront(n); } private void rewriteFunctionDeclarationsInBlock(Node block) { checkState(block.isBlock()); Node next = null; for (Node current = block.getFirstChild(); current != null; current = next) { // Get the next node now, because the current node will move and that will change its next // sibling. next = current.getNext(); if (NodeUtil.isFunctionDeclaration(current)) { rewriteFunctionDeclaration(current); } } } /** * @param marker The marker to add synthetic blocks for. */ private void addBlocks(Marker marker) { // Add a block around the template section so that // START // BODY // END // is transformed to: // BLOCK (synthetic) // START // BLOCK (synthetic) // BODY // END // This prevents the start or end markers from mingling with the code in the block body. Node outerBlock = IR.block().srcref(marker.startMarker); outerBlock.setIsSyntheticBlock(true); outerBlock.insertBefore(marker.startMarker); Node innerBlock = IR.block().srcref(marker.startMarker); innerBlock.setIsSyntheticBlock(true); // Move everything after the start node up to the end node into the inner block. moveSiblingExclusive(innerBlock, marker.startMarker, marker.endMarker); // Add the start node. outerBlock.addChildToBack(outerBlock.getNext().detach()); // Add the inner block. outerBlock.addChildToBack(innerBlock); // And finally the end node. outerBlock.addChildToBack(outerBlock.getNext().detach()); // NOTE: Moving the code into a block made sense prior to ES2015 when declarations were never // block scoped. But with ES2015+ function, class, let and const are all block scoped. // Here we are only rewriting functions because this is the behavior that "normalize" previously // had and we aren't currently (July 2020) trying to improve this code's behavior. This // maintains the status quo and allows the normalization of function to be removed. rewriteFunctionDeclarationsInBlock(innerBlock); compiler.reportChangeToEnclosingScope(outerBlock); } /** * Move the nodes between start and end from the source block to the destination block. If start * is null, move the first child of the block. If end is null, move the last child of the block. */ private void moveSiblingExclusive(Node dest, Node start, Node end) { checkNotNull(start); checkNotNull(end); while (start.getNext() != end) { Node child = start.getNext().detach(); dest.addChildToBack(child); } } @Override public void visit(NodeTraversal unused, Node n, Node parent) { if (!n.isCall() || !n.getFirstChild().isName()) { return; } Node callTarget = n.getFirstChild(); String callName = callTarget.getString(); if (startMarkerName.equals(callName)) { if (!parent.isExprResult()) { compiler.report(JSError.make(n, INVALID_MARKER_USAGE, startMarkerName)); return; } markerStack.push(parent); return; } if (!endMarkerName.equals(callName)) { return; } Node endMarkerNode = parent; if (!endMarkerNode.isExprResult()) { compiler.report(JSError.make(n, INVALID_MARKER_USAGE, endMarkerName)); return; } if (markerStack.isEmpty()) { compiler.report(JSError.make(n, UNMATCHED_END_MARKER, startMarkerName, endMarkerName)); return; } Node startMarkerNode = markerStack.pop(); if (endMarkerNode.getParent() != startMarkerNode.getParent()) { // The end marker isn't in the same block as the start marker. compiler.report(JSError.make(n, UNMATCHED_END_MARKER, startMarkerName, endMarkerName)); return; } // This is a valid marker, add it to the list of markers to process. validMarkers.add(new Marker(startMarkerNode, endMarkerNode)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy