JS: fix incremental generation of source maps

This commit is contained in:
Alexey Andreev
2017-02-28 20:41:27 +03:00
parent 0058d2fdf6
commit f4a9b99e4d
6 changed files with 56 additions and 70 deletions
@@ -464,7 +464,9 @@ public class JsToStringGenerationVisitor extends JsVisitor {
rightParen(); rightParen();
nestedPush(x.getBody()); nestedPush(x.getBody());
accept(x.getBody()); if (x.getBody() != null) {
accept(x.getBody());
}
nestedPop(x.getBody()); nestedPop(x.getBody());
} }
@@ -19,11 +19,7 @@ import java.util.Map;
public final class JsProgram extends SourceInfoAwareJsNode { public final class JsProgram extends SourceInfoAwareJsNode {
private final JsGlobalBlock globalBlock = new JsGlobalBlock(); private final JsGlobalBlock globalBlock = new JsGlobalBlock();
private final TDoubleObjectHashMap<JsDoubleLiteral> doubleLiteralMap = new TDoubleObjectHashMap<JsDoubleLiteral>();
private final TIntObjectHashMap<JsIntLiteral> intLiteralMap = new TIntObjectHashMap<JsIntLiteral>();
private final JsRootScope rootScope; private final JsRootScope rootScope;
private final Map<String, JsStringLiteral> stringLiteralMap = new THashMap<String, JsStringLiteral>();
private final JsObjectScope topScope; private final JsObjectScope topScope;
public JsProgram() { public JsProgram() {
@@ -36,23 +32,11 @@ public final class JsProgram extends SourceInfoAwareJsNode {
} }
public JsNumberLiteral getNumberLiteral(double value) { public JsNumberLiteral getNumberLiteral(double value) {
JsDoubleLiteral literal = doubleLiteralMap.get(value); return new JsDoubleLiteral(value);
if (literal == null) {
literal = new JsDoubleLiteral(value);
doubleLiteralMap.put(value, literal);
}
return literal;
} }
public JsNumberLiteral getNumberLiteral(int value) { public JsNumberLiteral getNumberLiteral(int value) {
JsIntLiteral literal = intLiteralMap.get(value); return new JsIntLiteral(value);
if (literal == null) {
literal = new JsIntLiteral(value);
intLiteralMap.put(value, literal);
}
return literal;
} }
/** /**
@@ -77,12 +61,7 @@ public final class JsProgram extends SourceInfoAwareJsNode {
*/ */
@NotNull @NotNull
public JsStringLiteral getStringLiteral(String value) { public JsStringLiteral getStringLiteral(String value) {
JsStringLiteral literal = stringLiteralMap.get(value); return new JsStringLiteral(value);
if (literal == null) {
literal = new JsStringLiteral(value);
stringLiteralMap.put(value, literal);
}
return literal;
} }
@Override @Override
@@ -31,7 +31,7 @@ class JsAstDeserializer(private val program: JsProgram) {
private val stringTable = mutableListOf<String>() private val stringTable = mutableListOf<String>()
private val nameTable = mutableListOf<Name>() private val nameTable = mutableListOf<Name>()
private val nameCache = mutableListOf<JsName?>() private val nameCache = mutableListOf<JsName?>()
private val locationStack: Deque<JsLocation> = ArrayDeque() private val fileStack: Deque<String> = ArrayDeque()
fun deserialize(input: InputStream): JsProgramFragment { fun deserialize(input: InputStream): JsProgramFragment {
return deserialize(Chunk.parseFrom(CodedInputStream.newInstance(input).apply { setRecursionLimit(4096) })) return deserialize(Chunk.parseFrom(CodedInputStream.newInstance(input).apply { setRecursionLimit(4096) }))
@@ -491,28 +491,27 @@ class JsAstDeserializer(private val program: JsProgram) {
} }
private fun <T : JsNode> withLocation(fileId: Int?, location: Location?, action: () -> T): T { private fun <T : JsNode> withLocation(fileId: Int?, location: Location?, action: () -> T): T {
val lastLocation = locationStack.peek() val deserializedFile = fileId?.let { deserializeString(it) }
val deserializedLocation = if (fileId != null || location != null) { val file = deserializedFile ?: fileStack.peek()
val file = fileId?.let { deserializeString(it) } ?: lastLocation?.file!! val deserializedLocation = if (file != null && location != null) {
val (startLine, startChar) = if (location != null) { JsLocation(file, location.startLine, location.startChar)
Pair(location.startLine, location.startChar)
}
else {
Pair(lastLocation.startLine, lastLocation.startChar)
}
JsLocation(file, startLine, startChar)
} }
else { else {
null null
} }
if (deserializedLocation != null) { val shouldUpdateFile = location != null && deserializedFile != null && deserializedFile != fileStack.peek()
locationStack.push(deserializedLocation) if (shouldUpdateFile) {
fileStack.push(deserializedFile)
} }
val node = action() val node = action()
if (deserializedLocation != null) { if (deserializedLocation != null) {
node.source = deserializedLocation if (node !is JsLiteral.JsBooleanLiteral && node !is JsLiteral.JsThisRef && node !is JsNullLiteral) {
locationStack.pop() node.source = deserializedLocation
}
}
if (shouldUpdateFile) {
fileStack.pop()
} }
return node return node
@@ -31,7 +31,7 @@ class JsAstSerializer {
private val stringTableBuilder = StringTable.newBuilder() private val stringTableBuilder = StringTable.newBuilder()
private val nameMap = mutableMapOf<JsName, Int>() private val nameMap = mutableMapOf<JsName, Int>()
private val stringMap = mutableMapOf<String, Int>() private val stringMap = mutableMapOf<String, Int>()
private val locationStack: Deque<JsLocation> = ArrayDeque() private val fileStack: Deque<String> = ArrayDeque()
fun serialize(fragment: JsProgramFragment, output: OutputStream) { fun serialize(fragment: JsProgramFragment, output: OutputStream) {
serialize(fragment).writeTo(output) serialize(fragment).writeTo(output)
@@ -561,27 +561,25 @@ class JsAstSerializer {
} }
private inline fun withLocation(node: JsNode, fileConsumer: (Int) -> Unit, locationConsumer: (Location) -> Unit, inner: () -> Unit) { private inline fun withLocation(node: JsNode, fileConsumer: (Int) -> Unit, locationConsumer: (Location) -> Unit, inner: () -> Unit) {
val lastLocation = locationStack.peek()
val location = extractLocation(node) val location = extractLocation(node)
val locationStackModified = if (lastLocation != location && location != null) { var fileChanged = false
locationStack.push(location) if (location != null) {
if (lastLocation == null || lastLocation.file != location.file) { val lastFile = fileStack.peek()
fileChanged = lastFile != location.file
if (fileChanged) {
fileConsumer(serialize(location.file)) fileConsumer(serialize(location.file))
fileStack.push(location.file)
} }
val locationBuilder = Location.newBuilder() val locationBuilder = Location.newBuilder()
locationBuilder.startLine = location.startLine locationBuilder.startLine = location.startLine
locationBuilder.startChar = location.startChar locationBuilder.startChar = location.startChar
locationConsumer(locationBuilder.build()) locationConsumer(locationBuilder.build())
true
}
else {
false
} }
inner() inner()
if (locationStackModified) { if (fileChanged) {
locationStack.pop() fileStack.pop()
} }
} }
@@ -270,10 +270,17 @@ abstract class BasicBoxTest(
translateFiles(allTranslationUnits, recompiledOutputFile, recompiledConfig) translateFiles(allTranslationUnits, recompiledOutputFile, recompiledConfig)
val originalOutput = FileUtil.loadFile(outputFile) val originalOutput = FileUtil.loadFile(outputFile)
val recompiledOutput = FileUtil.loadFile(recompiledOutputFile) val recompiledOutput = removeRecompiledSuffix(FileUtil.loadFile(recompiledOutputFile))
TestCase.assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput) TestCase.assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput)
val originalSourceMap = FileUtil.loadFile(File(outputFile.parentFile, outputFile.name + ".map"))
val recompiledSourceMap = removeRecompiledSuffix(
FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map")))
TestCase.assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap)
} }
}protected fun translateFiles(units: List<TranslationUnit>, outputFile: File, config: JsConfig, }
private fun removeRecompiledSuffix(text: String): String = text.replace("-recompiled.js", ".js")protected fun translateFiles(units: List<TranslationUnit>, outputFile: File, config: JsConfig,
outputPrefixFile: File?, outputPrefixFile: File?,
outputPostfixFile: File?, outputPostfixFile: File?,
mainCallParameters: MainCallParameters) { mainCallParameters: MainCallParameters) {
@@ -362,9 +369,11 @@ abstract class BasicBoxTest(
configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5) configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5)
configuration.put(JSConfigurationKeys.SOURCE_MAP, generateSourceMap) configuration.put(JSConfigurationKeys.SOURCE_MAP, generateSourceMap)
val hasFilesToRecompile = module.hasFilesToRecompile val hasFilesToRecompile = module.hasFilesToRecompile
configuration.put(JSConfigurationKeys.META_INFO, multiModule) configuration.put(JSConfigurationKeys.META_INFO, multiModule)
configuration.put(JSConfigurationKeys.SERIALIZE_FRAGMENTS, hasFilesToRecompile) configuration.put(JSConfigurationKeys.SERIALIZE_FRAGMENTS, hasFilesToRecompile)
configuration.put(JSConfigurationKeys.SOURCE_MAP, hasFilesToRecompile)
if (additionalMetadata != null) { if (additionalMetadata != null) {
configuration.put(JSConfigurationKeys.FALLBACK_METADATA, additionalMetadata.map { FileUtil.loadFileBytes(it) }) configuration.put(JSConfigurationKeys.FALLBACK_METADATA, additionalMetadata.map { FileUtil.loadFileBytes(it) })
@@ -413,9 +422,7 @@ abstract class BasicBoxTest(
currentModule.languageVersion = LanguageVersion.fromVersionString(version) currentModule.languageVersion = LanguageVersion.fromVersionString(version)
} }
return TestFile(temporaryFile.absolutePath, currentModule).apply { return TestFile(temporaryFile.absolutePath, currentModule, recompile = RECOMPILE_PATTERN.matcher(text).find())
recompile = RECOMPILE_PATTERN.matcher(text).find()
}
} }
override fun createModule(name: String, dependencies: List<String>): TestModule? { override fun createModule(name: String, dependencies: List<String>): TestModule? {
@@ -427,12 +434,10 @@ abstract class BasicBoxTest(
} }
} }
private class TestFile(val fileName: String, val module: TestModule) { private class TestFile(val fileName: String, val module: TestModule, val recompile: Boolean) {
init { init {
module.files += this module.files += this
} }
var recompile = false
} }
private class TestModule( private class TestModule(
@@ -20,22 +20,25 @@ import com.intellij.openapi.editor.Document;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.util.PairConsumer; import com.intellij.util.PairConsumer;
import org.jetbrains.kotlin.js.backend.ast.JsLocation;
import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder; import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder;
class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder, Object> { class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder, Object> {
@Override @Override
public void consume(SourceMapBuilder builder, Object sourceInfo) { public void consume(SourceMapBuilder builder, Object sourceInfo) {
if (!(sourceInfo instanceof PsiElement)) { if (sourceInfo instanceof PsiElement) {
return; PsiElement element = (PsiElement) sourceInfo;
PsiFile file = element.getContainingFile();
int offset = element.getNode().getStartOffset();
Document document = file.getViewProvider().getDocument();
assert document != null;
int line = document.getLineNumber(offset);
int column = offset - document.getLineStartOffset(line);
builder.addMapping(file.getViewProvider().getVirtualFile().getPath(), line, column);
}
else if (sourceInfo instanceof JsLocation) {
JsLocation location = (JsLocation) sourceInfo;
builder.addMapping(location.getFile(), location.getStartLine(), location.getStartChar());
} }
PsiElement element = (PsiElement) sourceInfo;
PsiFile file = element.getContainingFile();
int offset = element.getNode().getStartOffset();
Document document = file.getViewProvider().getDocument();
assert document != null;
int line = document.getLineNumber(offset);
int column = offset - document.getLineStartOffset(line);
builder.addMapping(file.getViewProvider().getVirtualFile().getPath(), line, column);
} }
} }