com.google.javascript.jscomp.SourceMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler Show documentation
Show all versions of closure-compiler Show documentation
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.
/*
* 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 com.google.common.base.Predicate;
import com.google.debugging.sourcemap.FilePosition;
import com.google.debugging.sourcemap.SourceMapFormat;
import com.google.debugging.sourcemap.SourceMapGenerator;
import com.google.debugging.sourcemap.SourceMapGeneratorFactory;
import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping;
import com.google.javascript.rhino.Node;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* Collects information mapping the generated (compiled) source back to
* its original source for debugging purposes.
*
* @see CodeConsumer
* @see CodeGenerator
* @see CodePrinter
*
* @author [email protected] (John Lenz)
*/
public final class SourceMap {
private static final Logger logger =
Logger.getLogger("com.google.javascript.jscomp");
/**
* An enumeration of available source map formats
*/
public static enum Format {
DEFAULT {
@Override SourceMap getInstance() {
return new SourceMap(
SourceMapGeneratorFactory.getInstance(SourceMapFormat.DEFAULT));
}
},
V3 {
@Override SourceMap getInstance() {
return new SourceMap(
SourceMapGeneratorFactory.getInstance(SourceMapFormat.V3));
}
};
abstract SourceMap getInstance();
}
/**
* Source maps can be very large different levels of detail can be specified.
*/
public static enum DetailLevel implements Predicate {
// ALL is best when the fullest details are needed for debugging or for
// code-origin analysis.
ALL {
@Override public boolean apply(Node node) {
return true;
}
},
// SYMBOLS is intended to be used for stack trace deobfuscation when full
// detail is not needed.
SYMBOLS {
@Override public boolean apply(Node node) {
return node.isCall()
|| node.isNew()
|| node.isFunction()
|| node.isName()
|| NodeUtil.isGet(node)
|| NodeUtil.isObjectLitKey(node)
|| (node.isString() && NodeUtil.isGet(node.getParent()))
|| node.isTaggedTemplateLit();
}
}
}
/**
* A simple pair of path prefixes to the desired "destination" location to use within the source
* map.
*/
public static final class LocationMapping {
final String prefix;
final String replacement;
public LocationMapping(String prefix, String replacement) {
this.prefix = prefix;
this.replacement = replacement;
}
@Override
public String toString() {
return "(" + prefix + "|" + replacement + ")";
}
@Override
public boolean equals(Object other) {
if (other instanceof LocationMapping) {
return ((LocationMapping) other).prefix.equals(prefix)
&& ((LocationMapping) other).replacement.equals(replacement);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(prefix, replacement);
}
}
private final SourceMapGenerator generator;
private List prefixMappings = Collections.emptyList();
private final Map sourceLocationFixupCache =
new HashMap<>();
/**
* A mapping derived from input source maps. Maps back to input sources that inputs to this
* compilation job have been generated from, and used to create a source map that maps all the way
* back to original inputs. {@code null} if no such mapping is wanted.
*/
@Nullable
private SourceFileMapping mapping;
private SourceMap(SourceMapGenerator generator) {
this.generator = generator;
}
public void addMapping(
Node node,
FilePosition outputStartPosition,
FilePosition outputEndPosition) {
String sourceFile = node.getSourceFileName();
// If the node does not have an associated source file or
// its line number is -1, then the node does not have sufficient
// information for a mapping to be useful.
if (sourceFile == null || node.getLineno() < 0) {
return;
}
int lineNo = node.getLineno();
int charNo = node.getCharno();
String originalName = node.getOriginalName();
if (mapping != null) {
OriginalMapping sourceMapping = mapping.getSourceMapping(sourceFile, lineNo, charNo);
if (sourceMapping != null) {
sourceFile = sourceMapping.getOriginalFile();
lineNo = sourceMapping.getLineNumber();
charNo = sourceMapping.getColumnPosition();
originalName = sourceMapping.getIdentifier();
}
}
sourceFile = fixupSourceLocation(sourceFile);
// Rhino source lines are one based but for v3 source maps, we make
// them zero based.
int lineBaseOffset = 1;
generator.addMapping(
sourceFile, originalName,
new FilePosition(lineNo - lineBaseOffset, charNo),
outputStartPosition, outputEndPosition);
}
public void addSourceFile(SourceFile sourceFile) {
try {
generator.addSourcesContent(fixupSourceLocation(sourceFile.getName()), sourceFile.getCode());
} catch (IOException e) {
logger.log(Level.WARNING, "Exception while adding source content to source map.", e);
}
}
/**
* @param sourceFile The source file location to fixup.
* @return a remapped source file.
*/
private String fixupSourceLocation(String sourceFile) {
if (prefixMappings.isEmpty()) {
return sourceFile;
}
String fixed = sourceLocationFixupCache.get(sourceFile);
if (fixed != null) {
return fixed;
}
// Replace the first prefix found with its replacement
for (LocationMapping mapping : prefixMappings) {
if (sourceFile.startsWith(mapping.prefix)) {
fixed = mapping.replacement + sourceFile.substring(
mapping.prefix.length());
break;
}
}
// If none of the mappings match then use the original file path.
if (fixed == null) {
fixed = sourceFile;
}
sourceLocationFixupCache.put(sourceFile, fixed);
return fixed;
}
public void appendTo(Appendable out, String name) throws IOException {
generator.appendTo(out, fixupSourceLocation(name));
}
public void reset() {
generator.reset();
sourceLocationFixupCache.clear();
}
public void setStartingPosition(int offsetLine, int offsetIndex) {
generator.setStartingPosition(offsetLine, offsetIndex);
}
public void setWrapperPrefix(String prefix) {
generator.setWrapperPrefix(prefix);
}
public void validate(boolean validate) {
generator.validate(validate);
}
/**
* @param sourceMapLocationMappings
*/
public void setPrefixMappings(List sourceMapLocationMappings) {
this.prefixMappings = sourceMapLocationMappings;
}
public void setSourceFileMapping(SourceFileMapping mapping) {
this.mapping = mapping;
}
}