com.google.javascript.jscomp.CoverageInstrumentationCallback Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.frontend.js.minifier
Show all versions of com.liferay.frontend.js.minifier
Liferay Frontend JS Minifier
/*
* 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 com.google.common.annotations.GwtIncompatible;
import com.google.javascript.jscomp.CoverageInstrumentationPass.CoverageReach;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import java.util.Map;
/**
* This class implements a traversal to instrument an AST for code coverage.
* @author [email protected] (Praveen Kumashi)
*
*/
@GwtIncompatible("FileInstrumentationData")
class CoverageInstrumentationCallback extends
NodeTraversal.AbstractPostOrderCallback {
private final AbstractCompiler compiler;
private final Map instrumentationData;
private final CoverageReach reach;
static final String ARRAY_NAME_PREFIX = "JSCompiler_lcov_data_";
public CoverageInstrumentationCallback(
AbstractCompiler compiler,
Map instrumentationData,
CoverageReach reach) {
this.compiler = compiler;
this.instrumentationData = instrumentationData;
this.reach = reach;
}
/**
* Returns the name of the source file from which the given node originates.
* @param traversal the traversal
* @return the name of the file it originates from
*/
private static String getFileName(NodeTraversal traversal) {
return traversal.getSourceName();
}
/**
* Returns a string that can be used as array name. The name is based on the
* source filename of the AST node.
*/
private String createArrayName(NodeTraversal traversal) {
return ARRAY_NAME_PREFIX
+ CoverageUtil.createIdentifierFromText(getFileName(traversal));
}
/**
* Creates and return a new instrumentation node. The instrumentation Node is
* of the form: "arrayName[lineNumber] = true;"
* Note 1: Node returns a 1-based line number.
* Note 2: Line numbers in instrumentation are made 0-based. This is done to
* map to their bit representation in BitField. Thus, there's a one-to-one
* correspondence of the line number seen on instrumented files and their bit
* encodings.
*
* @return an instrumentation node corresponding to the line number
*/
private Node newInstrumentationNode(NodeTraversal traversal, Node node) {
int lineNumber = node.getLineno();
String arrayName = createArrayName(traversal);
// Create instrumentation Node
Node getElemNode = IR.getelem(
IR.name(arrayName),
IR.number(lineNumber - 1)); // Make line number 0-based
Node exprNode = IR.exprResult(IR.assign(getElemNode, IR.trueNode()));
// Note line as instrumented
String fileName = getFileName(traversal);
if (!instrumentationData.containsKey(fileName)) {
instrumentationData.put(fileName,
new FileInstrumentationData(fileName, arrayName));
}
instrumentationData.get(fileName).setLineAsInstrumented(lineNumber);
return exprNode.useSourceInfoIfMissingFromForTree(node);
}
/**
* Create and return a new array declaration node. The array name is
* generated based on the source filename, and declaration is of the form:
* "var arrayNameUsedInFile = [];"
*/
private Node newArrayDeclarationNode(NodeTraversal traversal) {
return IR.var(
IR.name(createArrayName(traversal)),
IR.arraylit());
}
/**
* @return a Node containing file specific setup logic.
*/
private Node newHeaderNode(NodeTraversal traversal, Node srcref) {
String fileName = getFileName(traversal);
String arrayName = createArrayName(traversal);
FileInstrumentationData data = instrumentationData.get(fileName);
checkNotNull(data);
String objName = CoverageInstrumentationPass.JS_INSTRUMENTATION_OBJECT_NAME;
// var JSCompiler_lcov_data_xx = [];
// __jscov.executedLines.push(JSCompiler_lcov_data_xx);
// __jscov.instrumentedLines.push(hex-data);
// __jscov.fileNames.push(filename);
return IR.block(
newArrayDeclarationNode(traversal),
IR.exprResult(
IR.call(
NodeUtil.newQName(compiler, objName + ".executedLines.push"),
IR.name(arrayName))),
IR.exprResult(
IR.call(
NodeUtil.newQName(compiler, objName + ".instrumentedLines.push"),
IR.string(data.getInstrumentedLinesAsHexString()))),
IR.exprResult(
IR.call(
NodeUtil.newQName(compiler, objName + ".fileNames.push"), IR.string(fileName))))
.useSourceInfoIfMissingFromForTree(srcref);
}
/**
* Instruments the JS code by inserting appropriate nodes into the AST. The
* instrumentation logic is tuned to collect "line coverage" data only.
*/
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
// SCRIPT node is special - it is the root of the AST for code from a file.
// Append code to declare and initialize structures used in instrumentation.
if (node.isScript()) {
String fileName = getFileName(traversal);
if (instrumentationData.get(fileName) != null) {
node.addChildrenToFront(newHeaderNode(traversal, node).removeChildren());
}
traversal.reportCodeChange();
return;
}
// Don't instrument global statements
if (reach == CoverageReach.CONDITIONAL
&& parent != null && parent.isScript()) {
return;
}
// For arrow functions whose body is an expression instead of a block,
// convert it to a block so that it can be instrumented.
if (node.isFunction() && !NodeUtil.getFunctionBody(node).isNormalBlock()) {
Node returnValue = NodeUtil.getFunctionBody(node);
Node body = IR.block(IR.returnNode(returnValue.detach()));
body.useSourceInfoIfMissingFromForTree(returnValue);
node.addChildToBack(body);
}
// Add instrumentation code just before a function block.
// Similarly before other constructs: 'with', 'case', 'default', 'catch'
if (node.isFunction()
|| node.isWith()
|| node.isCase()
|| node.isDefaultCase()
|| node.isCatch()) {
Node codeBlock = node.getLastChild();
codeBlock.addChildToFront(
newInstrumentationNode(traversal, node));
traversal.reportCodeChange();
return;
}
// Add instrumentation code as the first child of a 'try' block.
if (node.isTry()) {
Node firstChild = node.getFirstChild();
firstChild.addChildToFront(
newInstrumentationNode(traversal, node));
traversal.reportCodeChange();
return;
}
// For any other statement, add instrumentation code just before it.
if (parent != null && NodeUtil.isStatementBlock(parent)) {
parent.addChildBefore(
newInstrumentationNode(traversal, node),
node);
traversal.reportCodeChange();
return;
}
}
}