Add test infrastructure for JS source map parser and generator

This commit is contained in:
Alexey Andreev
2017-05-23 12:24:16 +03:00
parent ca738a9536
commit c5bf76578a
9 changed files with 156 additions and 54 deletions
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.js.test
import com.google.gwt.dev.js.ThrowExceptionOnErrorReporter
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.StandardFileSystems
@@ -31,19 +32,19 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.config.EcmaVersion
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.dce.DeadCodeElimination
import org.jetbrains.kotlin.js.dce.InputFile
import org.jetbrains.kotlin.js.facade.K2JSTranslator
import org.jetbrains.kotlin.js.facade.MainCallParameters
import org.jetbrains.kotlin.js.facade.TranslationResult
import org.jetbrains.kotlin.js.facade.TranslationUnit
import org.jetbrains.kotlin.js.test.utils.DirectiveTestUtils
import org.jetbrains.kotlin.js.test.utils.JsTestUtils
import org.jetbrains.kotlin.js.test.utils.verifyAst
import org.jetbrains.kotlin.js.facade.*
import org.jetbrains.kotlin.js.parser.parse
import org.jetbrains.kotlin.js.parser.sourcemaps.*
import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.js.test.utils.*
import org.jetbrains.kotlin.js.util.TextOutputImpl
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPsiFactory
@@ -55,10 +56,7 @@ import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactory
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.utils.DFS
import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.File
import java.io.PrintStream
import java.io.*
import java.nio.charset.Charset
import java.util.regex.Pattern
@@ -386,6 +384,7 @@ abstract class BasicBoxTest(
}
processJsProgram(translationResult.program, units.filterIsInstance<TranslationUnit.SourceFile>().map { it.file })
checkSourceMap(outputFile, translationResult.program)
}
protected fun processJsProgram(program: JsProgram, psiFiles: List<KtFile>) {
@@ -395,6 +394,56 @@ abstract class BasicBoxTest(
program.verifyAst()
}
private fun checkSourceMap(outputFile: File, program: JsProgram) {
val generatedProgram = JsProgram()
generatedProgram.globalBlock.statements += program.globalBlock.statements.map { it.deepCopy() }
generatedProgram.accept(object : RecursiveJsVisitor() {
override fun visitObjectLiteral(x: JsObjectLiteral) {
super.visitObjectLiteral(x)
x.isMultiline = false
}
override fun visitVars(x: JsVars) {
x.isMultiline = false
super.visitVars(x)
}
})
removeLocationFromBlocks(generatedProgram)
generatedProgram.accept(AmbiguousAstSourcePropagation())
val output = TextOutputImpl()
val sourceMapBuilder = SourceMap3Builder(outputFile, output, SourceMapBuilderConsumer())
generatedProgram.accept(JsSourceGenerationVisitor(output, sourceMapBuilder))
val code = output.toString()
val generatedSourceMap = sourceMapBuilder.build()
val codeWithLines = generatedProgram.toStringWithLineNumbers()
val parsedProgram = JsProgram()
parsedProgram.globalBlock.statements += parse(code, ThrowExceptionOnErrorReporter, parsedProgram.scope, outputFile.path)
removeLocationFromBlocks(parsedProgram)
val sourceMapParseResult = SourceMapParser.parse(StringReader(generatedSourceMap))
val sourceMap = when (sourceMapParseResult) {
is SourceMapSuccess -> sourceMapParseResult.value
is SourceMapError -> error("Could not parse source map: ${sourceMapParseResult.message}")
}
val remapper = SourceMapLocationRemapper(mapOf(outputFile.path to sourceMap))
remapper.remap(parsedProgram)
val codeWithRemappedLines = parsedProgram.toStringWithLineNumbers()
TestCase.assertEquals(codeWithLines, codeWithRemappedLines)
}
private fun removeLocationFromBlocks(program: JsProgram) {
program.globalBlock.accept(object : RecursiveJsVisitor() {
override fun visitBlock(x: JsBlock) {
super.visitBlock(x)
x.source = null
}
})
}
private fun createPsiFile(fileName: String): KtFile {
val psiManager = PsiManager.getInstance(project)
val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)
@@ -422,12 +471,10 @@ abstract class BasicBoxTest(
configuration.put(JSConfigurationKeys.MODULE_KIND, module.moduleKind)
configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5)
configuration.put(JSConfigurationKeys.SOURCE_MAP, generateSourceMap)
val hasFilesToRecompile = module.hasFilesToRecompile
configuration.put(JSConfigurationKeys.META_INFO, multiModule)
configuration.put(JSConfigurationKeys.SERIALIZE_FRAGMENTS, hasFilesToRecompile)
configuration.put(JSConfigurationKeys.SOURCE_MAP, hasFilesToRecompile)
configuration.put(JSConfigurationKeys.SOURCE_MAP, hasFilesToRecompile || generateSourceMap)
if (additionalMetadata != null) {
val metadata = PackagesWithHeaderMetadata(
@@ -31,11 +31,21 @@ class LineCollector : RecursiveJsVisitor() {
}
private fun handleNodeLocation(node: JsNode) {
(node.source as? PsiElement)?.let { source ->
val file = source.containingFile
val offset = source.node.startOffset
val document = file.viewProvider.document!!
val line = document.getLineNumber(offset)
val source = node.source
val line = when (source) {
is PsiElement -> {
val file = source.containingFile
val offset = source.node.startOffset
val document = file.viewProvider.document!!
document.getLineNumber(offset)
}
is JsLocation -> {
source.startLine
}
else -> null
}
if (line != null) {
currentStatement?.let {
val linesByStatement = lineNumbersByStatement.getOrPut(it, ::mutableListOf)
if (linesByStatement.lastOrNull() != line) {
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.js.test.utils
import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.js.util.TextOutputImpl
fun JsProgram.toStringWithLineNumbers(): String {
val output = TextOutputImpl()
val lineCollector = LineCollector().also { it.accept(this) }
LineOutputToStringVisitor(output, lineCollector).accept(this.globalBlock)
return output.toString()
}