diff --git a/src/com/google/javascript/jscomp/CoverageInstrumentationCallback.java b/src/com/google/javascript/jscomp/CoverageInstrumentationCallback.java index e514a0d7966..3ec29e31427 100644 --- a/src/com/google/javascript/jscomp/CoverageInstrumentationCallback.java +++ b/src/com/google/javascript/jscomp/CoverageInstrumentationCallback.java @@ -33,16 +33,17 @@ class CoverageInstrumentationCallback extends NodeTraversal.AbstractPostOrderCallback { + private final AbstractCompiler compiler; private final Map instrumentationData; - - private CoverageReach reach; + 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; } @@ -61,8 +62,8 @@ private static String getFileName(NodeTraversal traversal) { * source filename of the AST node. */ private String createArrayName(NodeTraversal traversal) { - return ARRAY_NAME_PREFIX + - CoverageUtil.createIdentifierFromText(getFileName(traversal)); + return ARRAY_NAME_PREFIX + + CoverageUtil.createIdentifierFromText(getFileName(traversal)); } /** @@ -78,27 +79,25 @@ private String createArrayName(NodeTraversal traversal) { * node is needed * @return an instrumentation node corresponding to the line number */ - private Node newInstrumentationNode(NodeTraversal traversal, int lineNumber) { - String fileName = getFileName(traversal); + private Node newInstrumentationNode(NodeTraversal traversal, Node node) { + int lineNumber = node.getLineno(); String arrayName = createArrayName(traversal); // Create instrumentation Node - // arr[line] = true; - Node nameNode = IR.name(arrayName); - Node numNode = IR.number(lineNumber - 1); // Make line number 0-based - Node getElemNode = IR.getelem(nameNode, numNode); - Node trueNode = IR.trueNode(); - Node assignNode = IR.assign(getElemNode, trueNode); - Node exprNode = IR.exprResult(assignNode); + 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; + return exprNode.useSourceInfoIfMissingFromForTree(node); } /** @@ -107,17 +106,15 @@ private Node newInstrumentationNode(NodeTraversal traversal, int lineNumber) { * "var arrayNameUsedInFile = [];" */ private Node newArrayDeclarationNode(NodeTraversal traversal) { - Node arraylitNode = IR.arraylit(); - Node nameNode = IR.name(createArrayName(traversal)); - nameNode.addChildToFront(arraylitNode); - Node varNode = IR.var(nameNode); - return varNode; + return IR.var( + IR.name(createArrayName(traversal)), + IR.arraylit()); } /** * @return a Node containing file specific setup logic. */ - private Node newHeaderNode(NodeTraversal traversal) { + private Node newHeaderNode(NodeTraversal traversal, Node srcref) { String fileName = getFileName(traversal); String arrayName = createArrayName(traversal); FileInstrumentationData data = instrumentationData.get(fileName); @@ -139,7 +136,7 @@ private Node newHeaderNode(NodeTraversal traversal) { IR.getprop( IR.name("JSCompiler_lcov_fileNames"), IR.string("push")), - IR.string(fileName)))); + IR.string(fileName)))).useSourceInfoIfMissingFromForTree(srcref); } /** @@ -153,8 +150,9 @@ public void visit(NodeTraversal traversal, Node node, Node parent) { if (node.isScript()) { String fileName = getFileName(traversal); if (instrumentationData.get(fileName) != null) { - node.addChildToFront(newHeaderNode(traversal)); + node.addChildToFront(newHeaderNode(traversal, node)); } + compiler.reportCodeChange(); return; } @@ -166,14 +164,15 @@ public void visit(NodeTraversal traversal, Node node, Node parent) { // 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()) { + if (node.isFunction() + || node.isWith() + || node.isCase() + || node.isDefaultCase() + || node.isCatch()) { Node codeBlock = node.getLastChild(); codeBlock.addChildToFront( - newInstrumentationNode(traversal, node.getLineno())); + newInstrumentationNode(traversal, node)); + compiler.reportCodeChange(); return; } @@ -181,14 +180,17 @@ public void visit(NodeTraversal traversal, Node node, Node parent) { if (node.isTry()) { Node firstChild = node.getFirstChild(); firstChild.addChildToFront( - newInstrumentationNode(traversal, node.getLineno())); + newInstrumentationNode(traversal, node)); + compiler.reportCodeChange(); return; } // For any other statement, add instrumentation code just before it. if (parent != null && NodeUtil.isStatementBlock(parent)) { parent.addChildBefore( - newInstrumentationNode(traversal, node.getLineno()), node); + newInstrumentationNode(traversal, node), + node); + compiler.reportCodeChange(); return; } } diff --git a/src/com/google/javascript/jscomp/CoverageInstrumentationPass.java b/src/com/google/javascript/jscomp/CoverageInstrumentationPass.java index e1564a81406..a33219d340c 100644 --- a/src/com/google/javascript/jscomp/CoverageInstrumentationPass.java +++ b/src/com/google/javascript/jscomp/CoverageInstrumentationPass.java @@ -39,10 +39,10 @@ class CoverageInstrumentationPass implements CompilerPass { private Map instrumentationData; private CoverageReach reach; - private static final String JS_INSTRUMENTATION_EXTERNS_CODE = - "var JSCompiler_lcov_executedLines;\n" + - "var JSCompiler_lcov_instrumentedLines;\n" + - "var JSCompiler_lcov_fileNames;\n"; + private static final String JS_INSTRUMENTATION_EXTERNS_CODE = "" + + "var JSCompiler_lcov_executedLines;\n" + + "var JSCompiler_lcov_instrumentedLines;\n" + + "var JSCompiler_lcov_fileNames;\n"; public enum CoverageReach { ALL, @@ -66,11 +66,11 @@ public CoverageInstrumentationPass(AbstractCompiler compiler, */ private void addHeaderCode(Node script) { script.addChildToFront( - createConditionalVarDecl("JSCompiler_lcov_executedLines")); + createConditionalVarDecl("JSCompiler_lcov_executedLines", script)); script.addChildToFront( - createConditionalVarDecl("JSCompiler_lcov_instrumentedLines")); + createConditionalVarDecl("JSCompiler_lcov_instrumentedLines", script)); script.addChildToFront( - createConditionalVarDecl("JSCompiler_lcov_fileNames")); + createConditionalVarDecl("JSCompiler_lcov_fileNames", script)); } /** @@ -89,7 +89,8 @@ private Node getInstrumentationExternsNode() { public void process(Node externsNode, Node rootNode) { if (rootNode.hasChildren()) { NodeTraversal.traverseEs6(compiler, rootNode, - new CoverageInstrumentationCallback(instrumentationData, reach)); + new CoverageInstrumentationCallback( + compiler, instrumentationData, reach)); Node firstScript = rootNode.getFirstChild(); Preconditions.checkState(firstScript.isScript()); @@ -99,7 +100,7 @@ public void process(Node externsNode, Node rootNode) { externsNode.addChildToBack(getInstrumentationExternsNode()); } - private static Node createConditionalVarDecl(String name) { + private static Node createConditionalVarDecl(String name, Node srcref) { Node var = IR.var( IR.name(name), IR.or( @@ -109,6 +110,6 @@ private static Node createConditionalVarDecl(String name) { JSDocInfoBuilder builder = new JSDocInfoBuilder(false); builder.recordSuppressions(ImmutableSet.of("duplicate")); var.setJSDocInfo(builder.build()); - return var; + return var.useSourceInfoIfMissingFromForTree(srcref); } } diff --git a/src/com/google/javascript/jscomp/FileInstrumentationData.java b/src/com/google/javascript/jscomp/FileInstrumentationData.java index 28d986091c0..d7e39f5dc3a 100644 --- a/src/com/google/javascript/jscomp/FileInstrumentationData.java +++ b/src/com/google/javascript/jscomp/FileInstrumentationData.java @@ -82,9 +82,8 @@ String getInstrumentedLinesAsHexString() { * @param lineNumber the line number which was instrumented */ void setLineAsInstrumented(int lineNumber) { - Preconditions.checkArgument(lineNumber > 0, - "Expected non-zero positive integer as line " + - "number."); + Preconditions.checkArgument( + lineNumber > 0, "Expected non-zero positive integer as line number: %s", lineNumber); // Map the 1-based line number to 0-based bit position instrumentedBits.set(lineNumber - 1); diff --git a/test/com/google/javascript/jscomp/CoverageInstrumentationPassTest.java b/test/com/google/javascript/jscomp/CoverageInstrumentationPassTest.java new file mode 100644 index 00000000000..d369d553489 --- /dev/null +++ b/test/com/google/javascript/jscomp/CoverageInstrumentationPassTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 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; + +/** + * Tests for {@link CoverageInstrumentationPass}. + */ +public final class CoverageInstrumentationPassTest extends CompilerTestCase { + @Override + protected CompilerPass getProcessor(Compiler compiler) { + return new CoverageInstrumentationPass(compiler, CoverageInstrumentationPass.CoverageReach.ALL); + } + + @Override + public void setUp() { +// enableLineNumberCheck(false); + allowExternsChanges(true); + } + + public void testFunction() { + test( + "function f() { console.log('hi'); }", + LINE_JOINER.join( + "/** @suppress {duplicate} */", + "var JSCompiler_lcov_fileNames = JSCompiler_lcov_fileNames || [];", + "/** @suppress {duplicate} */", + "var JSCompiler_lcov_instrumentedLines = JSCompiler_lcov_instrumentedLines||[];", + "/** @suppress {duplicate} */", + "var JSCompiler_lcov_executedLines = JSCompiler_lcov_executedLines||[];", + "{", + " var JSCompiler_lcov_data_testcode = [];", + " JSCompiler_lcov_executedLines.push(JSCompiler_lcov_data_testcode);", + " JSCompiler_lcov_instrumentedLines.push('01');", + " JSCompiler_lcov_fileNames.push('testcode');", + "}", + "JSCompiler_lcov_data_testcode[0] = true;", + "/** @suppress {duplicate} */", + "var JSCompiler_lcov_fileNames = JSCompiler_lcov_fileNames||[];", + "JSCompiler_lcov_data_testcode[0] = true;", + "/** @suppress {duplicate} */", + "var JSCompiler_lcov_instrumentedLines = JSCompiler_lcov_instrumentedLines||[];", + "JSCompiler_lcov_data_testcode[0] = true;", + "/** @suppress {duplicate} */", + "var JSCompiler_lcov_executedLines = JSCompiler_lcov_executedLines||[];", + "JSCompiler_lcov_data_testcode[0] = true;", + "{", + " JSCompiler_lcov_data_testcode[0] = true;", + " var JSCompiler_lcov_data_testcode=[];", + " JSCompiler_lcov_data_testcode[0] = true;", + " JSCompiler_lcov_executedLines.push(JSCompiler_lcov_data_testcode);", + " JSCompiler_lcov_data_testcode[0] = true;", + " JSCompiler_lcov_instrumentedLines.push('01');", + " JSCompiler_lcov_data_testcode[0] = true;", + " JSCompiler_lcov_fileNames.push('testcode');", + "}", + "function f() {", + " JSCompiler_lcov_data_testcode[0] = true;", + " JSCompiler_lcov_data_testcode[0] = true;", + " JSCompiler_lcov_data_testcode[0] = true;", + " JSCompiler_lcov_data_testcode[0] = true;", + " JSCompiler_lcov_data_testcode[0] = true;", + " JSCompiler_lcov_data_testcode[0] = true;", + " console.log('hi');", + "}")); + } +}