320 lines
9.4 KiB
Java
320 lines
9.4 KiB
Java
/*
|
|
* Copyright 2011 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.debugging.sourcemap;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping;
|
|
import com.google.javascript.jscomp.Compiler;
|
|
import com.google.javascript.jscomp.CompilerOptions;
|
|
import com.google.javascript.jscomp.Result;
|
|
import com.google.javascript.jscomp.SourceFile;
|
|
import com.google.javascript.jscomp.SourceMap;
|
|
import com.google.javascript.jscomp.SourceMap.DetailLevel;
|
|
|
|
import junit.framework.TestCase;
|
|
|
|
import java.io.IOException;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
|
|
/**
|
|
* @author johnlenz@google.com (John Lenz)
|
|
*/
|
|
public abstract class SourceMapTestCase extends TestCase {
|
|
|
|
private boolean validateColumns = true;
|
|
|
|
public SourceMapTestCase() {
|
|
}
|
|
|
|
void disableColumnValidation() {
|
|
validateColumns = false;
|
|
}
|
|
|
|
|
|
static final List<SourceFile> EXTERNS = ImmutableList.of(
|
|
SourceFile.fromCode("externs", ""));
|
|
|
|
protected DetailLevel detailLevel = SourceMap.DetailLevel.ALL;
|
|
|
|
protected static class RunResult {
|
|
String generatedSource;
|
|
SourceMap sourceMap;
|
|
public String sourceMapFileContent;
|
|
}
|
|
|
|
protected static class Token {
|
|
final String tokenName;
|
|
final String inputName;
|
|
final FilePosition position;
|
|
Token(String tokenName, String inputName, FilePosition position) {
|
|
this.tokenName = tokenName;
|
|
this.inputName = inputName;
|
|
this.position = position;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setUp() {
|
|
detailLevel = SourceMap.DetailLevel.ALL;
|
|
}
|
|
|
|
/**
|
|
* Creates a source map for the given JS code and asserts it is
|
|
* equal to the expected golden map.
|
|
*/
|
|
protected void checkSourceMap(String js, String expectedMap)
|
|
throws IOException {
|
|
checkSourceMap("testcode", js, expectedMap);
|
|
}
|
|
|
|
protected String getSourceMap(RunResult result) throws IOException {
|
|
StringBuilder sb = new StringBuilder();
|
|
result.sourceMap.appendTo(sb, "testcode");
|
|
return sb.toString();
|
|
}
|
|
|
|
protected void checkSourceMap(String fileName, String js, String expectedMap)
|
|
throws IOException {
|
|
RunResult result = compile(js, fileName);
|
|
assertEquals(expectedMap, result.sourceMapFileContent);
|
|
assertEquals(result.sourceMapFileContent, getSourceMap(result));
|
|
}
|
|
|
|
/**
|
|
* Finds the all the __XX__ tokens in the given JavaScript
|
|
* string.
|
|
*/
|
|
private Map<String, Token> findTokens(Map<String, String> inputs) {
|
|
Map<String, Token> tokens = Maps.newLinkedHashMap();
|
|
|
|
for (Entry<String, String> entry : inputs.entrySet()) {
|
|
findTokens(tokens, entry.getKey(), entry.getValue());
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
/**
|
|
* Finds the all the __XX__ tokens in the given JavaScript
|
|
* string.
|
|
*/
|
|
private Map<String, Token> findTokens(String src) {
|
|
Map<String, Token> tokens = Maps.newLinkedHashMap();
|
|
|
|
findTokens(tokens, "", src);
|
|
|
|
return tokens;
|
|
}
|
|
|
|
/**
|
|
* Finds the all the __XX__ tokens in the given JavaScript
|
|
* string.
|
|
*/
|
|
private Map<String, Token> findTokens(
|
|
Map<String, Token> tokens, String inputName, String js) {
|
|
|
|
int currentLine = 0;
|
|
int positionOffset = 0;
|
|
|
|
for (int i = 0; i < js.length(); ++i) {
|
|
char current = js.charAt(i);
|
|
|
|
if (current == '\n') {
|
|
positionOffset = i + 1;
|
|
currentLine++;
|
|
continue;
|
|
}
|
|
|
|
if (current == '_' && (i < js.length() - 5)) {
|
|
// Check for the _ token.
|
|
if (js.charAt(i + 1) != '_') {
|
|
continue;
|
|
}
|
|
|
|
// Loop until we have another _ token.
|
|
String tokenName = "";
|
|
|
|
int j = i + 2;
|
|
for (; j < js.length(); ++j) {
|
|
if (js.charAt(j) == '_') {
|
|
break;
|
|
}
|
|
|
|
tokenName += js.charAt(j);
|
|
}
|
|
|
|
if (tokenName.length() > 0) {
|
|
int currentPosition = i - positionOffset;
|
|
Token token = new Token(
|
|
tokenName, inputName,
|
|
new FilePosition(currentLine, currentPosition));
|
|
tokens.put(tokenName, token);
|
|
}
|
|
|
|
i = j;
|
|
}
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
abstract protected SourceMap.Format getSourceMapFormat();
|
|
|
|
abstract protected SourceMapConsumer getSourceMapConsumer();
|
|
|
|
protected void compileAndCheck(String js) {
|
|
String inputName = "testcode";
|
|
RunResult result = compile(js, inputName);
|
|
check(inputName, js, result.generatedSource, result.sourceMapFileContent);
|
|
}
|
|
|
|
protected void check(
|
|
String inputName, String input, String output,
|
|
String sourceMapFileContent) {
|
|
Map<String, String> inputMap = new LinkedHashMap<String, String>();
|
|
inputMap.put(inputName, input);
|
|
check(inputMap, output, sourceMapFileContent);
|
|
}
|
|
|
|
protected void check(
|
|
Map<String, String> originalInputs, String generatedSource,
|
|
String sourceMapFileContent) {
|
|
check(originalInputs, generatedSource, sourceMapFileContent, null);
|
|
}
|
|
|
|
protected void check(
|
|
Map<String, String> originalInputs, String generatedSource,
|
|
String sourceMapFileContent, SourceMapSupplier supplier) {
|
|
// Find all instances of the __XXX__ pattern in the original
|
|
// source code.
|
|
Map<String, Token> originalTokens = findTokens(originalInputs);
|
|
|
|
// Find all instances of the __XXX__ pattern in the generated
|
|
// source code.
|
|
Map<String, Token> resultTokens = findTokens(generatedSource);
|
|
|
|
// Ensure that the generated instances match via the source map
|
|
// to the original source code.
|
|
|
|
// Ensure the token counts match.
|
|
assertEquals(originalTokens.size(), resultTokens.size());
|
|
|
|
SourceMapping reader;
|
|
try {
|
|
reader = SourceMapConsumerFactory.parse(sourceMapFileContent, supplier);
|
|
} catch (SourceMapParseException e) {
|
|
throw new RuntimeException("unexpected exception", e);
|
|
}
|
|
|
|
// Map the tokens from the generated source back to the
|
|
// input source and ensure that the map is correct.
|
|
for (Token token : resultTokens.values()) {
|
|
OriginalMapping mapping = reader.getMappingForLine(
|
|
token.position.getLine() + 1,
|
|
token.position.getColumn() + 1);
|
|
|
|
assertNotNull(mapping);
|
|
|
|
// Find the associated token in the input source.
|
|
Token inputToken = originalTokens.get(token.tokenName);
|
|
assertNotNull(inputToken);
|
|
assertEquals(mapping.getOriginalFile(), inputToken.inputName);
|
|
|
|
// Ensure that the map correctly points to the token (we add 1
|
|
// to normalize versus the Rhino line number indexing scheme).
|
|
assertEquals(mapping.getLineNumber(),
|
|
inputToken.position.getLine() + 1);
|
|
|
|
int start = inputToken.position.getColumn() + 1;
|
|
if (inputToken.tokenName.startsWith("STR")) {
|
|
// include the preceding quote.
|
|
start--;
|
|
}
|
|
|
|
if (validateColumns) {
|
|
assertEquals(start, mapping.getColumnPosition());
|
|
}
|
|
|
|
// Ensure that if the token name does not being with an 'STR' (meaning a
|
|
// string) it has an original name.
|
|
if (!inputToken.tokenName.startsWith("STR")) {
|
|
assertTrue("missing name for " + inputToken.tokenName,
|
|
!mapping.getIdentifier().isEmpty());
|
|
}
|
|
|
|
// Ensure that if the mapping has a name, it matches the token.
|
|
if (!mapping.getIdentifier().isEmpty()) {
|
|
assertEquals(mapping.getIdentifier(),
|
|
"__" + inputToken.tokenName + "__");
|
|
}
|
|
}
|
|
}
|
|
|
|
protected RunResult compile(String js, String fileName) {
|
|
return compile(js, fileName, null, null);
|
|
}
|
|
|
|
protected CompilerOptions getCompilerOptions() {
|
|
CompilerOptions options = new CompilerOptions();
|
|
options.sourceMapOutputPath = "testcode_source_map.out";
|
|
options.sourceMapFormat = getSourceMapFormat();
|
|
options.sourceMapDetailLevel = detailLevel;
|
|
return options;
|
|
}
|
|
|
|
protected RunResult compile(
|
|
String js1, String fileName1, String js2, String fileName2) {
|
|
Compiler compiler = new Compiler();
|
|
CompilerOptions options = getCompilerOptions();
|
|
|
|
// Turn on IDE mode to get rid of optimizations.
|
|
options.ideMode = true;
|
|
|
|
List<SourceFile> inputs =
|
|
ImmutableList.of(SourceFile.fromCode(fileName1, js1));
|
|
|
|
if (js2 != null && fileName2 != null) {
|
|
inputs = ImmutableList.of(
|
|
SourceFile.fromCode(fileName1, js1),
|
|
SourceFile.fromCode(fileName2, js2));
|
|
}
|
|
|
|
Result result = compiler.compile(EXTERNS, inputs, options);
|
|
|
|
assertTrue("compilation failed", result.success);
|
|
String source = compiler.toSource();
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
try {
|
|
result.sourceMap.validate(true);
|
|
result.sourceMap.appendTo(sb, "testcode");
|
|
} catch (IOException e) {
|
|
throw new RuntimeException("unexpected exception", e);
|
|
}
|
|
|
|
RunResult rr = new RunResult();
|
|
rr.generatedSource = source;
|
|
rr.sourceMap = result.sourceMap;
|
|
rr.sourceMapFileContent = sb.toString();
|
|
return rr;
|
|
}
|
|
|
|
}
|