Makes per-file-compilation compatible with SourceMaps.
Change-Id: Ic4a5ab7a7e9327ef4cadef6e95cb9e5452e1258f
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/JsSourceMapBuilder.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/JsSourceMapBuilder.java
new file mode 100644
index 0000000..793dca5
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/JsSourceMapBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * 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.gwt.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.dev.jjs.JsSourceMap;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+
+import java.util.LinkedHashMap;
+import java.util.Map.Entry;
+
+/**
+ * A builder for combining existing JS source maps.
+ * <p>
+ * Takes care of rebasing the offset of appended SourceMaps onto the end of the previously
+ * accumulated ranges.
+ */
+public class JsSourceMapBuilder {
+
+ private int bytes;
+ private int lines;
+ private final LinkedHashMap<Range, SourceInfo> sourceInfosByRange = Maps.newLinkedHashMap();
+
+ public void append(JsSourceMap typeSourceMap) {
+ for (Entry<Range, SourceInfo> entry : typeSourceMap.getEntries()) {
+ Range normalizedRange = entry.getKey();
+ Range outputOffsetRange = normalizedRange.createOffsetCopy(bytes, lines);
+ SourceInfo sourceInfo = entry.getValue();
+ sourceInfosByRange.put(outputOffsetRange, sourceInfo);
+ }
+ bytes += typeSourceMap.getBytes();
+ lines += typeSourceMap.getLines();
+ }
+
+ public JsSourceMap build() {
+ return new JsSourceMap(sourceInfosByRange, bytes, lines);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/JsSourceMapExtractor.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/JsSourceMapExtractor.java
new file mode 100644
index 0000000..0d4b721
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/JsSourceMapExtractor.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * 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.gwt.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.soyc.Range;
+import com.google.gwt.dev.jjs.JsSourceMap;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * An efficient JS source map chunk extractor.
+ * <p>
+ * Efficient extraction comes with the restriction that extracted ranges must be consecutive to
+ * avoid having to seek within the subject JS source map data.
+ */
+public class JsSourceMapExtractor {
+
+ private int lastTypeEndPosition;
+ private LinkedList<Entry<Range, SourceInfo>> rangeToSourceInfoMappings = Lists.newLinkedList();
+
+ public JsSourceMapExtractor(Map<Range, SourceInfo> sourceInfosByRange) {
+ rangeToSourceInfoMappings.addAll(sourceInfosByRange.entrySet());
+ }
+
+ public JsSourceMap extract(int typeStartPosition, int typeEndPosition, int typeStartLineNumber,
+ int typeEndLineNumber) {
+ assert !rangeToSourceInfoMappings
+ .isEmpty() : "Source mappings can't be extracted past the end.";
+ skipTo(typeStartPosition);
+
+ lastTypeEndPosition = typeEndPosition;
+
+ LinkedHashMap<Range, SourceInfo> typeSourceMappings = Maps.newLinkedHashMap();
+
+ do {
+ Entry<Range, SourceInfo> rangeAndSourceInfo = rangeToSourceInfoMappings.getFirst();
+ Range range = rangeAndSourceInfo.getKey();
+ SourceInfo sourceInfo = rangeAndSourceInfo.getValue();
+
+ if (range.getStart() < typeStartPosition || range.getStart() >= typeEndPosition) {
+ break;
+ }
+ if (range.getEnd() <= typeStartPosition || range.getEnd() > typeEndPosition) {
+ break;
+ }
+
+ rangeToSourceInfoMappings.removeFirst();
+
+ // Normalize range position relative to the beginning of the type that contains it.
+ Range typeOffsetNormalizedRange =
+ range.createNormalizedCopy(typeStartPosition, typeStartLineNumber);
+ typeSourceMappings.put(typeOffsetNormalizedRange, sourceInfo);
+ } while (!rangeToSourceInfoMappings.isEmpty());
+
+ int typeBytes = typeEndPosition - typeStartPosition;
+ int typeLines = typeEndLineNumber - typeStartLineNumber;
+ return new JsSourceMap(typeSourceMappings, typeBytes, typeLines);
+ }
+
+ private void skipTo(int rangeEndPosition) {
+ assert lastTypeEndPosition <= rangeEndPosition : "You can only skip forward.";
+
+ do {
+ Range range = rangeToSourceInfoMappings.getFirst().getKey();
+
+ if (range.getStart() >= rangeEndPosition) {
+ break;
+ }
+ if (range.getEnd() > rangeEndPosition) {
+ break;
+ }
+
+ rangeToSourceInfoMappings.removeFirst();
+ } while (!rangeToSourceInfoMappings.isEmpty());
+ }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/NamedRange.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/NamedRange.java
index 603f9c6..33bcd4a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/NamedRange.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/NamedRange.java
@@ -14,22 +14,31 @@
package com.google.gwt.core.ext.linker.impl;
/**
- * A starting and ending byte range with associated name.
+ * A starting and ending byte range/line number with associated name.
*/
public class NamedRange {
+ private int endLineNumber;
private int endPosition;
private final String name;
+ private int startLineNumber;
private int startPosition;
public NamedRange(String name) {
this.name = name;
}
- public NamedRange(String name, int startPosition, int endPosition) {
+ public NamedRange(String name, int startPosition, int endPosition, int startLineNumber,
+ int endLineNumber) {
this.name = name;
this.startPosition = startPosition;
this.endPosition = endPosition;
+ this.startLineNumber = startLineNumber;
+ this.endLineNumber = endLineNumber;
+ }
+
+ public int getEndLineNumber() {
+ return endLineNumber;
}
public int getEndPosition() {
@@ -40,14 +49,26 @@
return name;
}
+ public int getStartLineNumber() {
+ return startLineNumber;
+ }
+
public int getStartPosition() {
return startPosition;
}
+ public void setEndLineNumber(int endLineNumber) {
+ this.endLineNumber = endLineNumber;
+ }
+
public void setEndPosition(int endPosition) {
this.endPosition = endPosition;
}
+ public void setStartLineNumber(int startLineNumber) {
+ this.startLineNumber = startLineNumber;
+ }
+
public void setStartPosition(int startPosition) {
this.startPosition = startPosition;
}
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/Range.java b/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
index 0d66bdb..8e20ae0 100644
--- a/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
@@ -57,11 +57,11 @@
};
final int end;
- final int endLine;
final int endColumn;
+ final int endLine;
final int start;
- final int startLine;
final int startColumn;
+ final int startLine;
/**
* Constructor.
@@ -95,19 +95,40 @@
}
/**
- * Returns a copy with the end moved.
- */
- public Range withNewEnd(int newEnd, int newEndLine, int newEndColumn) {
- return new Range(start, newEnd, startLine, startColumn, newEndLine, newEndColumn);
- }
-
- /**
* Return <code>true</code> if the given Range lies wholly within the Range.
*/
public boolean contains(Range o) {
return start <= o.start && o.end <= end;
}
+ /**
+ * Creates a Range copy whose start position and line number have been rebased relative to some
+ * base position.
+ * <p>
+ * For example a range that starts at byte 5342 when normalized against a base start of 5000 will
+ * now start at byte 342.
+ */
+ public Range createNormalizedCopy(int baseStart, int baseStartLine) {
+ if (start < 0) {
+ return new Range(baseStartLine, baseStartLine, baseStartLine, baseStartLine, baseStart,
+ baseStartLine);
+ }
+
+ return createOffsetCopy(-baseStart, -baseStartLine);
+ }
+
+ /**
+ * Creates a Range copy whose start position and line number have been moved by some known offset
+ * size.
+ * <p>
+ * For example a range that starts at byte 342 when moved by an offset of 5000 will now start at
+ * byte 5342.
+ */
+ public Range createOffsetCopy(int positionOffset, int lineOffset) {
+ return new Range(start + positionOffset, end + positionOffset, startLine + lineOffset,
+ startColumn, endLine + lineOffset, endColumn);
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Range)) {
@@ -157,4 +178,11 @@
public String toString() {
return "[" + start + " - " + end + ")";
}
+
+ /**
+ * Returns a copy with the end moved.
+ */
+ public Range withNewEnd(int newEnd, int newEndLine, int newEndColumn) {
+ return new Range(start, newEnd, startLine, startColumn, newEndLine, newEndColumn);
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
index b3aba0e..874e416 100644
--- a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
+++ b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.ext.linker.StatementRanges;
import com.google.gwt.dev.javac.CompilationUnit;
import com.google.gwt.dev.javac.CompiledClass;
+import com.google.gwt.dev.jjs.JsSourceMap;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.dev.jjs.ast.JTypeOracle.ImmediateTypeRelations;
@@ -54,6 +55,7 @@
// GWT.create() rewrites and JSO Devirtualization.
private final Map<String, String> jsByTypeName = Maps.newHashMap();
private final Multimap<String, String> referencedTypeNamesByTypeName = HashMultimap.create();
+ private final Map<String, JsSourceMap> sourceMapsByTypeName = Maps.newHashMap();
private final Map<String, StatementRanges> statementRangesByTypeName = Maps.newHashMap();
private final Multimap<String, String> typeNamesByReferencingTypeName = HashMultimap.create();
@@ -101,6 +103,10 @@
return statementRangesByTypeName.get(typeName);
}
+ public JsSourceMap getSourceMap(String typeName) {
+ return sourceMapsByTypeName.get(typeName);
+ }
+
public void removeReferencesFrom(String fromTypeName) {
Collection<String> toTypeNames = referencedTypeNamesByTypeName.get(fromTypeName);
for (String toTypeName : toTypeNames) {
@@ -114,6 +120,10 @@
jsByTypeName.put(typeName, typeJs);
}
+ public void setSourceMapForType(String typeName, JsSourceMap sourceMap) {
+ sourceMapsByTypeName.put(typeName, sourceMap);
+ }
+
public void setStatementRangesForType(String typeName, StatementRanges statementRanges) {
statementRangesByTypeName.put(typeName, statementRanges);
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JsSourceMap.java b/dev/core/src/com/google/gwt/dev/jjs/JsSourceMap.java
index a4b9f01..aad0b71 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JsSourceMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JsSourceMap.java
@@ -15,34 +15,62 @@
*/
package com.google.gwt.dev.jjs;
+import com.google.gwt.core.ext.linker.impl.JsSourceMapExtractor;
import com.google.gwt.core.ext.soyc.Range;
+import java.util.Collection;
import java.util.Collections;
-import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.Map.Entry;
import java.util.Set;
/**
- * An unmodifiable container of mappings from one JavaScript file to the
- * Java code it came from.
+ * An unmodifiable container of mappings from one JavaScript file to the Java code it came from.
*
* (This class doesn't implement Map because we only use a few methods.)
*/
public class JsSourceMap {
- private final Map<Range, SourceInfo> delegate;
- public JsSourceMap(Map<Range, SourceInfo> delegate) {
- this.delegate = delegate;
+ private final int bytes;
+ private final int lines;
+ /**
+ * Maps JS ranges to Java ranges. The mapping is sparse thus the need for separately tracking
+ * total bytes and lines. Entries are ordered so that it is possible to extract and separate
+ * chunks in an efficient way.
+ */
+ private final LinkedHashMap<Range, SourceInfo> sourceInfosByRange;
+
+ public JsSourceMap(LinkedHashMap<Range, SourceInfo> sourceInfosByRange, int bytes, int lines) {
+ this.sourceInfosByRange = sourceInfosByRange;
+ this.bytes = bytes;
+ this.lines = lines;
}
- public Set<Range> keySet() {
- return Collections.unmodifiableSet(delegate.keySet());
+ public JsSourceMapExtractor createExtractor() {
+ return new JsSourceMapExtractor(sourceInfosByRange);
}
public SourceInfo get(Range key) {
- return delegate.get(key);
+ return sourceInfosByRange.get(key);
+ }
+
+ public int getBytes() {
+ return bytes;
+ }
+
+ public Collection<Entry<Range, SourceInfo>> getEntries() {
+ return sourceInfosByRange.entrySet();
+ }
+
+ public int getLines() {
+ return lines;
+ }
+
+ public Set<Range> keySet() {
+ return Collections.unmodifiableSet(sourceInfosByRange.keySet());
}
public int size() {
- return delegate.size();
+ return sourceInfosByRange.size();
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsFunctionClusterer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsFunctionClusterer.java
index 26d3b8f..3a93f39 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsFunctionClusterer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsFunctionClusterer.java
@@ -27,6 +27,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.regex.Pattern;
@@ -201,7 +202,7 @@
// iterate over expression ranges and shift
- Map<Range, SourceInfo> updatedInfoMap = new HashMap<Range, SourceInfo>();
+ LinkedHashMap<Range, SourceInfo> updatedInfoMap = new LinkedHashMap<Range, SourceInfo>();
Range entireProgram =
new Range(0, oldStatementRanges[oldStatementRanges.length - 1].getEnd());
for (int i = 0, j = 0; j < oldExpressionRanges.length; j++) {
@@ -227,7 +228,8 @@
updatedInfoMap.put(newExpressionRange, sourceInfoMap.get(oldExpressionRange));
}
- sourceInfoMap = new JsSourceMap(updatedInfoMap);
+ sourceInfoMap =
+ new JsSourceMap(updatedInfoMap, sourceInfoMap.getBytes(), sourceInfoMap.getLines());
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
index 7aa61d4..b8b02b5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
@@ -15,10 +15,13 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.linker.StatementRanges;
+import com.google.gwt.core.ext.linker.impl.JsSourceMapBuilder;
+import com.google.gwt.core.ext.linker.impl.JsSourceMapExtractor;
import com.google.gwt.core.ext.linker.impl.NamedRange;
import com.google.gwt.core.ext.linker.impl.StatementRangesBuilder;
import com.google.gwt.core.ext.linker.impl.StatementRangesExtractor;
import com.google.gwt.dev.MinimalRebuildCache.PermutationRebuildCache;
+import com.google.gwt.dev.jjs.JsSourceMap;
import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
@@ -31,8 +34,8 @@
* Transforms program JS source by performing a per-type link.
* <p>
* Provided JS and Ranges are used to grab new JS type chunks. A RebuildCache is used to cache per
- * type JS and statement ranges (possibly across compiles) and calculate the set of reachable types.
- * JTypeOracle is used to order linked output.
+ * type JS, statement ranges and sourcemaps (possibly across compiles) and calculate the set of
+ * reachable types. JTypeOracle is used to order linked output.
*/
public class JsTypeLinker extends JsAbstractTextTransformer {
@@ -44,7 +47,9 @@
private final Set<String> linkedTypeNames = Sets.newHashSet();
private TreeLogger logger;
private final PermutationRebuildCache permutationRebuildCache;
+ private final JsSourceMapExtractor jsSourceMapExtractor;
private final StatementRangesBuilder statementRangesBuilder = new StatementRangesBuilder();
+ private final JsSourceMapBuilder jsSourceMapBuilder = new JsSourceMapBuilder();
private final StatementRangesExtractor statementRangesExtractor;
private final JTypeOracle typeOracle;
private final List<NamedRange> typeRanges;
@@ -55,9 +60,12 @@
super(textTransformer);
this.logger = logger;
this.statementRangesExtractor = new StatementRangesExtractor(statementRanges);
+ this.jsSourceMapExtractor = sourceInfoMap.createExtractor();
this.typeRanges = typeRanges;
- this.headerRange = new NamedRange(HEADER_NAME, 0, programTypeRange.getStartPosition());
- this.footerRange = new NamedRange(FOOTER_NAME, programTypeRange.getEndPosition(), js.length());
+ this.headerRange = new NamedRange(HEADER_NAME, 0, programTypeRange.getStartPosition(), 0,
+ programTypeRange.getStartLineNumber());
+ this.footerRange = new NamedRange(FOOTER_NAME, programTypeRange.getEndPosition(), js.length(),
+ programTypeRange.getEndLineNumber(), sourceInfoMap.getLines());
this.permutationRebuildCache = permutationRebuildCache;
this.typeOracle = typeOracle;
}
@@ -71,7 +79,7 @@
@Override
protected void updateSourceInfoMap() {
- // TODO(stalcup): update sourcemaps to match relinking.
+ // Already updated in exec();
}
private List<String> computeReachableTypes() {
@@ -87,6 +95,9 @@
js.substring(typeRange.getStartPosition(), typeRange.getEndPosition()));
permutationRebuildCache.setStatementRangesForType(typeName,
statementRangesExtractor.extract(typeRange.getStartPosition(), typeRange.getEndPosition()));
+ permutationRebuildCache.setSourceMapForType(typeName, jsSourceMapExtractor.extract(
+ typeRange.getStartPosition(), typeRange.getEndPosition(), typeRange.getStartLineNumber(),
+ typeRange.getEndLineNumber()));
}
private void linkAll(List<String> reachableTypeNames) {
@@ -109,9 +120,14 @@
linkOne(FOOTER_NAME);
logger.log(TreeLogger.INFO, "prelink JS size = " + js.length());
+ logger.log(TreeLogger.INFO, "prelink sourcemap = " + sourceInfoMap.getBytes() + " bytes and "
+ + sourceInfoMap.getLines() + " lines");
js = jsBuilder.toString();
statementRanges = statementRangesBuilder.build();
+ sourceInfoMap = jsSourceMapBuilder.build();
logger.log(TreeLogger.INFO, "postlink JS size = " + js.length());
+ logger.log(TreeLogger.INFO, "postlink sourcemap = " + sourceInfoMap.getBytes() + " bytes and "
+ + sourceInfoMap.getLines() + " lines");
}
private void linkOne(String typeName) {
@@ -133,9 +149,10 @@
logger.log(TreeLogger.SPAM, "linking type " + typeName + " (" + typeJs.length() + " bytes)");
StatementRanges typeStatementRanges = permutationRebuildCache.getStatementRanges(typeName);
+ JsSourceMap typeSourceMap = permutationRebuildCache.getSourceMap(typeName);
jsBuilder.append(typeJs);
statementRangesBuilder.append(typeStatementRanges);
- // TODO(stalcup): build a sourcemap one type at a time.
+ jsSourceMapBuilder.append(typeSourceMap);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
index bff7973..8d99ec2 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java
@@ -31,9 +31,8 @@
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
/**
* A variation on the standard source generation visitor that records the
@@ -41,7 +40,8 @@
*/
public class JsReportGenerationVisitor extends
JsSourceGenerationVisitorWithSizeBreakdown {
- private final Map<Range, SourceInfo> sourceInfoMap = new HashMap<Range, SourceInfo>();
+ private final LinkedHashMap<Range, SourceInfo> sourceInfoMap =
+ new LinkedHashMap<Range, SourceInfo>();
private final TextOutput out;
private final boolean needSourcemapNames;
@@ -156,6 +156,6 @@
@Override
public JsSourceMap getSourceInfoMap() {
- return new JsSourceMap(sourceInfoMap);
+ return new JsSourceMap(sourceInfoMap, out.getPosition(), out.getLine());
}
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
index dfedfb5..e8371a2 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -314,21 +314,25 @@
== null : "Class start and end boundaries must be matched and not nested.";
currentClassRange = new NamedRange(x.getName());
currentClassRange.setStartPosition(p.getPosition());
+ currentClassRange.setStartLineNumber(p.getLine());
break;
case CLASS_END:
assert currentClassRange
!= null : "Class start and end boundaries must be matched and not nested.";
currentClassRange.setEndPosition(p.getPosition());
+ currentClassRange.setEndLineNumber(p.getLine());
classRanges.add(currentClassRange);
currentClassRange = null;
break;
case PROGRAM_START:
programClassRange = new NamedRange("Program");
programClassRange.setStartPosition(p.getPosition());
+ programClassRange.setStartLineNumber(p.getLine());
break;
case PROGRAM_END:
assert programClassRange != null : "Program start and end boundaries must be matched.";
programClassRange.setEndPosition(p.getPosition());
+ programClassRange.setEndLineNumber(p.getLine());
break;
default:
assert false : x.getType() + " position type is not recognized.";
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsTypeLinkerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsTypeLinkerTest.java
index 0eb4dac..af020a0 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsTypeLinkerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsTypeLinkerTest.java
@@ -14,23 +14,33 @@
package com.google.gwt.dev.jjs.impl;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.linker.impl.JsSourceMapBuilder;
import com.google.gwt.core.ext.linker.impl.NamedRange;
import com.google.gwt.core.ext.linker.impl.StatementRangesBuilder;
+import com.google.gwt.core.ext.soyc.Range;
import com.google.gwt.dev.MinimalRebuildCache;
import com.google.gwt.dev.MinimalRebuildCache.PermutationRebuildCache;
+import com.google.gwt.dev.jjs.JsSourceMap;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
import junit.framework.TestCase;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
/**
* Tests for {@link JsTypeLinker}.
*/
public class JsTypeLinkerTest extends TestCase {
+ private int lines;
+
public void testLink() {
NamedRange programRange = new NamedRange("Program");
NamedRange someModelARange = new NamedRange("com.some.app.SomeAModel");
@@ -40,24 +50,27 @@
List<NamedRange> classRanges =
Lists.newArrayList(someModelARange, someModelBRange, someControllerRange, entryPointRange);
StatementRangesBuilder srb = new StatementRangesBuilder();
+ JsSourceMapBuilder smb = new JsSourceMapBuilder();
// Build the original JS and log boundaries.
StringBuilder sb = new StringBuilder();
- appendStatement(sb, srb, "<preamble>");
- appendStatement(sb, srb, "<java.lang.Object />");
- appendStatement(sb, srb, "<java.lang.Class />");
- appendStatement(sb, srb, "</preamble>");
+ appendStatement(sb, srb, smb, "<preamble>\n");
+ appendStatement(sb, srb, smb, "<java.lang.Object />\n");
+ appendStatement(sb, srb, smb, "<java.lang.Class />\n");
+ appendStatement(sb, srb, smb, "</preamble>\n");
{
programRange.setStartPosition(sb.length());
- appendTypeStatement(sb, srb, someModelARange, "<com.some.app.SomeModelA>");
- appendTypeStatement(sb, srb, someModelBRange, "<com.some.app.SomeModelB>");
- appendTypeStatement(sb, srb, someControllerRange, "<com.some.app.SomeController>");
- appendTypeStatement(sb, srb, entryPointRange, "<com.some.app.EntryPoint>");
+ programRange.setStartLineNumber(lines);
+ appendTypeStatement(sb, srb, smb, someModelARange, "<com.some.app.SomeModelA>\n");
+ appendTypeStatement(sb, srb, smb, someModelBRange, "<com.some.app.SomeModelB>\n");
+ appendTypeStatement(sb, srb, smb, someControllerRange, "<com.some.app.SomeController>\n");
+ appendTypeStatement(sb, srb, smb, entryPointRange, "<com.some.app.EntryPoint>\n");
programRange.setEndPosition(sb.length());
+ programRange.setEndLineNumber(lines);
}
- appendStatement(sb, srb, "<epilogue>");
- appendStatement(sb, srb, "<Some Bootstrap Code>");
- appendStatement(sb, srb, "</epilogue>");
+ appendStatement(sb, srb, smb, "<epilogue>\n");
+ appendStatement(sb, srb, smb, "<Some Bootstrap Code>\n");
+ appendStatement(sb, srb, smb, "</epilogue>\n");
String originalJs = sb.toString();
MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache();
@@ -85,28 +98,38 @@
"com.some.app.SomeAModel");
JsTypeLinker jsTypeLinker = new JsTypeLinker(TreeLogger.NULL,
- new JsNoopTransformer(originalJs, srb.build(), null), classRanges, programRange,
+ new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange,
permutationRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
// Run the JS Type Linker.
jsTypeLinker.exec();
// Verify that the linker output all the expected classes and sorted them alphabetically.
- assertEquals("<preamble><java.lang.Object /><java.lang.Class /></preamble>"
- + "<com.some.app.EntryPoint>" + "<com.some.app.SomeModelA>" + "<com.some.app.SomeModelB>"
- + "<com.some.app.SomeController>" + "<epilogue><Some Bootstrap Code></epilogue>",
- jsTypeLinker.getJs());
+ assertEquals("<preamble>\n<java.lang.Object />\n<java.lang.Class />\n</preamble>\n"
+ + "<com.some.app.EntryPoint>\n" + "<com.some.app.SomeModelA>\n"
+ + "<com.some.app.SomeModelB>\n" + "<com.some.app.SomeController>\n"
+ + "<epilogue>\n<Some Bootstrap Code>\n</epilogue>\n", jsTypeLinker.getJs());
+ assertEquals(Lists.newArrayList("preamble", "java.lang.Object", "java.lang.Class", "/preamble",
+ "com.some.app.EntryPoint", "com.some.app.SomeModelA", "com.some.app.SomeModelB",
+ "com.some.app.SomeController", "epilogue", "Some Bootstrap Code", "/epilogue"),
+ getTypeNames(jsTypeLinker.getSourceInfoMap()));
+ assertEquals(11, jsTypeLinker.getSourceInfoMap().getLines());
// Make SomeModelB the super class of SomeModelA and then verify that B comes out before A.
superClassesByClass.put("com.some.app.SomeAModel", "com.some.app.SomeBModel");
jsTypeLinker = new JsTypeLinker(TreeLogger.NULL,
- new JsNoopTransformer(originalJs, srb.build(), null), classRanges, programRange,
+ new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange,
permutationRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
jsTypeLinker.exec();
- assertEquals("<preamble><java.lang.Object /><java.lang.Class /></preamble>"
- + "<com.some.app.EntryPoint>" + "<com.some.app.SomeModelB>" + "<com.some.app.SomeModelA>"
- + "<com.some.app.SomeController>" + "<epilogue><Some Bootstrap Code></epilogue>",
- jsTypeLinker.getJs());
+ assertEquals("<preamble>\n<java.lang.Object />\n<java.lang.Class />\n</preamble>\n"
+ + "<com.some.app.EntryPoint>\n" + "<com.some.app.SomeModelB>\n"
+ + "<com.some.app.SomeModelA>\n" + "<com.some.app.SomeController>\n"
+ + "<epilogue>\n<Some Bootstrap Code>\n</epilogue>\n", jsTypeLinker.getJs());
+ assertEquals(Lists.newArrayList("preamble", "java.lang.Object", "java.lang.Class", "/preamble",
+ "com.some.app.EntryPoint", "com.some.app.SomeModelB", "com.some.app.SomeModelA",
+ "com.some.app.SomeController", "epilogue", "Some Bootstrap Code", "/epilogue"),
+ getTypeNames(jsTypeLinker.getSourceInfoMap()));
+ assertEquals(11, jsTypeLinker.getSourceInfoMap().getLines());
// Stop referring to SomeModelA from the Controller and verify that SomeModelA is not in the
// output.
@@ -114,26 +137,52 @@
permutationRebuildCache.addTypeReference("com.some.app.SomeController",
"com.some.app.SomeBModel");
jsTypeLinker = new JsTypeLinker(TreeLogger.NULL,
- new JsNoopTransformer(originalJs, srb.build(), null), classRanges, programRange,
+ new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange,
permutationRebuildCache, new JTypeOracle(null, minimalRebuildCache, true));
jsTypeLinker.exec();
- assertEquals("<preamble><java.lang.Object /><java.lang.Class /></preamble>"
- + "<com.some.app.EntryPoint>" + "<com.some.app.SomeModelB>"
- + "<com.some.app.SomeController>" + "<epilogue><Some Bootstrap Code></epilogue>",
+ assertEquals("<preamble>\n<java.lang.Object />\n<java.lang.Class />\n</preamble>\n"
+ + "<com.some.app.EntryPoint>\n" + "<com.some.app.SomeModelB>\n"
+ + "<com.some.app.SomeController>\n" + "<epilogue>\n<Some Bootstrap Code>\n</epilogue>\n",
jsTypeLinker.getJs());
+ assertEquals(Lists.newArrayList("preamble", "java.lang.Object", "java.lang.Class", "/preamble",
+ "com.some.app.EntryPoint", "com.some.app.SomeModelB", "com.some.app.SomeController",
+ "epilogue", "Some Bootstrap Code", "/epilogue"),
+ getTypeNames(jsTypeLinker.getSourceInfoMap()));
+ assertEquals(10, jsTypeLinker.getSourceInfoMap().getLines());
}
private void appendStatement(StringBuilder sb, StatementRangesBuilder statementRangesBuilder,
- String statement) {
+ JsSourceMapBuilder jsSourceMapBuilder, String statement) {
+ String typeName =
+ statement.replace(" />", "").replace("<", "").replace(">", "").replace("\n", "");
+
statementRangesBuilder.addStartPosition(sb.length());
+ LinkedHashMap<Range, SourceInfo> sourceInfosByRange =
+ Maps.<Range, SourceInfo> newLinkedHashMap();
+ sourceInfosByRange.put(
+ new Range(0, statement.length(), lines, 0, lines + 1, statement.length()),
+ SourceOrigin.create(0, statement.length(), 0, typeName));
+ jsSourceMapBuilder.append(new JsSourceMap(sourceInfosByRange, statement.length(), 1));
sb.append(statement);
statementRangesBuilder.addEndPosition(sb.length());
+
+ lines++;
}
private void appendTypeStatement(StringBuilder sb, StatementRangesBuilder statementRangesBuilder,
- NamedRange someModelARange, String statement) {
+ JsSourceMapBuilder jsSourceMapBuilder, NamedRange someModelARange, String statement) {
someModelARange.setStartPosition(sb.length());
- appendStatement(sb, statementRangesBuilder, statement);
+ someModelARange.setStartLineNumber(lines);
+ appendStatement(sb, statementRangesBuilder, jsSourceMapBuilder, statement);
someModelARange.setEndPosition(sb.length());
+ someModelARange.setEndLineNumber(lines);
+ }
+
+ private List<String> getTypeNames(JsSourceMap sourceInfoMap) {
+ List<String> typeNames = Lists.newArrayList();
+ for (Entry<Range, SourceInfo> entry : sourceInfoMap.getEntries()) {
+ typeNames.add(entry.getValue().getFileName());
+ }
+ return typeNames;
}
}