Use source map remapper in JS inliner
This commit is contained in:
@@ -124,6 +124,7 @@ public class KotlinTestUtils {
|
||||
"(?://\\s*MODULE:\\s*([^()\\n]+)(?:\\(([^()]+(?:" + MODULE_DELIMITER + "[^()]+)*)\\))?\\s*(?:\\(([^()]+(?:" + MODULE_DELIMITER + "[^()]+)*)\\))?\\s*)?" +
|
||||
"//\\s*FILE:\\s*(.*)$", Pattern.MULTILINE);
|
||||
private static final Pattern DIRECTIVE_PATTERN = Pattern.compile("^//\\s*!([\\w_]+)(:\\s*(.*)$)?", Pattern.MULTILINE);
|
||||
private static final Pattern LINE_SEPARATOR_PATTERN = Pattern.compile("\\r\\n|\\r|\\n");
|
||||
|
||||
public static final BindingTrace DUMMY_TRACE = new BindingTrace() {
|
||||
@NotNull
|
||||
@@ -648,6 +649,12 @@ public class KotlinTestUtils {
|
||||
|
||||
@NotNull
|
||||
public static <M, F> List<F> createTestFiles(String testFileName, String expectedText, TestFileFactory<M, F> factory) {
|
||||
return createTestFiles(testFileName, expectedText, factory, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static <M, F> List<F> createTestFiles(String testFileName, String expectedText, TestFileFactory<M, F> factory,
|
||||
boolean preserveLocations) {
|
||||
Map<String, String> directives = parseDirectives(expectedText);
|
||||
|
||||
List<F> testFiles = Lists.newArrayList();
|
||||
@@ -681,7 +688,9 @@ public class KotlinTestUtils {
|
||||
else {
|
||||
end = expectedText.length();
|
||||
}
|
||||
String fileText = expectedText.substring(start, end);
|
||||
String fileText = preserveLocations ?
|
||||
substringKeepingLocations(expectedText, start, end) :
|
||||
expectedText.substring(start,end);
|
||||
processedChars = end;
|
||||
|
||||
testFiles.add(factory.createFile(module, fileName, fileText, directives));
|
||||
@@ -730,6 +739,26 @@ public class KotlinTestUtils {
|
||||
return testFiles;
|
||||
}
|
||||
|
||||
private static String substringKeepingLocations(String string, int start, int end) {
|
||||
Matcher matcher = LINE_SEPARATOR_PATTERN.matcher(string);
|
||||
StringBuilder prefix = new StringBuilder();
|
||||
int lastLineOffset = 0;
|
||||
while (matcher.find()) {
|
||||
if (matcher.end() > start) {
|
||||
break;
|
||||
}
|
||||
|
||||
lastLineOffset = matcher.end();
|
||||
prefix.append('\n');
|
||||
}
|
||||
|
||||
while (lastLineOffset++ < start) {
|
||||
prefix.append(' ');
|
||||
}
|
||||
|
||||
return prefix + string.substring(start, end);
|
||||
}
|
||||
|
||||
private static List<String> parseModuleList(@Nullable String dependencies) {
|
||||
if (dependencies == null) return Collections.emptyList();
|
||||
return StringsKt.split(dependencies, Pattern.compile(MODULE_DELIMITER), 0);
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.intellij.util.Processor
|
||||
import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
object JsLibraryUtils {
|
||||
@@ -43,49 +44,53 @@ object JsLibraryUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun traverseJsLibraries(libs: List<File>, action: (content: String, path: String) -> Unit) {
|
||||
@JvmStatic fun traverseJsLibraries(libs: List<File>, action: (JsLibrary) -> Unit) {
|
||||
libs.forEach { traverseJsLibrary(it, action) }
|
||||
}
|
||||
|
||||
@JvmStatic fun traverseJsLibrary(lib: File, action: (content: String, path: String) -> Unit) {
|
||||
@JvmStatic fun traverseJsLibrary(lib: File, action: (JsLibrary) -> Unit) {
|
||||
when {
|
||||
lib.isDirectory -> traverseDirectory(lib, action)
|
||||
FileUtil.isJarOrZip(lib) -> traverseArchive(lib, action)
|
||||
lib.name.endsWith(KotlinJavascriptMetadataUtils.JS_EXT) -> {
|
||||
lib.runIfFileExists(action)
|
||||
lib.runIfFileExists(lib.path, action)
|
||||
val jsFile = lib.withReplacedExtensionOrNull(
|
||||
KotlinJavascriptMetadataUtils.META_JS_SUFFIX, KotlinJavascriptMetadataUtils.JS_EXT
|
||||
)
|
||||
jsFile?.runIfFileExists(action)
|
||||
jsFile?.runIfFileExists(jsFile.path, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun File.runIfFileExists(action: (content: String, path: String) -> Unit) {
|
||||
private fun File.runIfFileExists(relativePath: String, action: (JsLibrary) -> Unit) {
|
||||
if (isFile) {
|
||||
action(FileUtil.loadFile(this), "")
|
||||
action(JsLibrary(readText(), relativePath, correspondingSourceMapFile().contentIfExists()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyJsFilesFromDirectory(dir: File, outputLibraryJsPath: String) {
|
||||
traverseDirectory(dir) { content, relativePath ->
|
||||
FileUtil.writeToFile(File(outputLibraryJsPath, relativePath), content)
|
||||
traverseDirectory(dir) { (content, path) ->
|
||||
FileUtil.writeToFile(File(outputLibraryJsPath, path), content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processDirectory(dir: File, action: (content: String, relativePath: String) -> Unit) {
|
||||
private fun File.contentIfExists(): String? = if (exists()) readText() else null
|
||||
|
||||
private fun File.correspondingSourceMapFile(): File = File(parentFile, name + ".map")
|
||||
|
||||
private fun processDirectory(dir: File, action: (JsLibrary) -> Unit) {
|
||||
FileUtil.processFilesRecursively(dir, Processor<File> { file ->
|
||||
val relativePath = FileUtil.getRelativePath(dir, file)
|
||||
?: throw IllegalArgumentException("relativePath should not be null $dir $file")
|
||||
if (file.isFile && relativePath.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
|
||||
if (relativePath.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
|
||||
val suggestedRelativePath = getSuggestedPath(relativePath) ?: return@Processor true
|
||||
action(FileUtil.loadFile(file), suggestedRelativePath)
|
||||
file.runIfFileExists(suggestedRelativePath, action)
|
||||
}
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
private fun traverseDirectory(dir: File, action: (content: String, relativePath: String) -> Unit) {
|
||||
private fun traverseDirectory(dir: File, action: (JsLibrary) -> Unit) {
|
||||
try {
|
||||
processDirectory(dir, action)
|
||||
}
|
||||
@@ -95,26 +100,48 @@ object JsLibraryUtils {
|
||||
}
|
||||
|
||||
private fun copyJsFilesFromZip(file: File, outputLibraryJsPath: String) {
|
||||
traverseArchive(file) { content, relativePath ->
|
||||
FileUtil.writeToFile(File(outputLibraryJsPath, relativePath), content)
|
||||
traverseArchive(file) { library ->
|
||||
FileUtil.writeToFile(File(outputLibraryJsPath, library.path), library.content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun traverseArchive(file: File, action: (content: String, relativePath: String) -> Unit) {
|
||||
private fun traverseArchive(file: File, action: (JsLibrary) -> Unit) {
|
||||
val zipFile = ZipFile(file.path)
|
||||
try {
|
||||
val zipEntries = zipFile.entries()
|
||||
val librariesWithoutSourceMaps = mutableListOf<JsLibrary>()
|
||||
val possibleMapFiles = mutableMapOf<String, ZipEntry>()
|
||||
|
||||
while (zipEntries.hasMoreElements()) {
|
||||
val entry = zipEntries.nextElement()
|
||||
val entryName = entry.name
|
||||
if (!entry.isDirectory && entryName.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
|
||||
val relativePath = getSuggestedPath(entryName) ?: continue
|
||||
if (!entry.isDirectory) {
|
||||
if (entryName.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
|
||||
val relativePath = getSuggestedPath(entryName) ?: continue
|
||||
|
||||
val stream = zipFile.getInputStream(entry)
|
||||
val content = FileUtil.loadTextAndClose(stream)
|
||||
action(content, relativePath)
|
||||
val stream = zipFile.getInputStream(entry)
|
||||
val content = FileUtil.loadTextAndClose(stream)
|
||||
librariesWithoutSourceMaps += JsLibrary(content, relativePath, null)
|
||||
}
|
||||
else if (entryName.endsWith(KotlinJavascriptMetadataUtils.JS_MAP_EXT)) {
|
||||
possibleMapFiles[entryName.removeSuffix(KotlinJavascriptMetadataUtils.JS_MAP_EXT)] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
librariesWithoutSourceMaps
|
||||
.map {
|
||||
val zipEntry = possibleMapFiles[it.path]
|
||||
if (zipEntry != null) {
|
||||
val stream = zipFile.getInputStream(zipEntry)
|
||||
val content = FileUtil.loadTextAndClose(stream)
|
||||
it.copy(sourceMapContent = content)
|
||||
}
|
||||
else {
|
||||
it
|
||||
}
|
||||
}
|
||||
.forEach(action)
|
||||
}
|
||||
catch (ex: IOException) {
|
||||
LOG.error("Could not extract files from archive ${file.name}: ${ex.message}")
|
||||
@@ -136,3 +163,5 @@ object JsLibraryUtils {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
data class JsLibrary(val content: String, val path: String, val sourceMapContent: String?)
|
||||
|
||||
@@ -57,6 +57,7 @@ class JsMetadataVersion(vararg numbers: Int) : BinaryVersion(*numbers) {
|
||||
object KotlinJavascriptMetadataUtils {
|
||||
const val JS_EXT: String = ".js"
|
||||
const val META_JS_SUFFIX: String = ".meta.js"
|
||||
const val JS_MAP_EXT: String = ".js.map"
|
||||
private val KOTLIN_JAVASCRIPT_METHOD_NAME = "kotlin_module_metadata"
|
||||
private val KOTLIN_JAVASCRIPT_METHOD_NAME_PATTERN = "\\.kotlin_module_metadata\\(".toPattern()
|
||||
|
||||
@@ -78,8 +79,8 @@ object KotlinJavascriptMetadataUtils {
|
||||
fun loadMetadata(file: File): List<KotlinJavascriptMetadata> {
|
||||
assert(file.exists()) { "Library $file not found" }
|
||||
val metadataList = arrayListOf<KotlinJavascriptMetadata>()
|
||||
JsLibraryUtils.traverseJsLibrary(file) { content, _ ->
|
||||
parseMetadata(content, metadataList)
|
||||
JsLibraryUtils.traverseJsLibrary(file) { library ->
|
||||
parseMetadata(library.content, metadataList)
|
||||
}
|
||||
|
||||
return metadataList
|
||||
|
||||
@@ -26,7 +26,9 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy
|
||||
import org.jetbrains.kotlin.js.config.JsConfig
|
||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
||||
import org.jetbrains.kotlin.js.inline.util.isCallInvocation
|
||||
import org.jetbrains.kotlin.js.parser.OffsetToSourceMapping
|
||||
import org.jetbrains.kotlin.js.parser.parseFunction
|
||||
import org.jetbrains.kotlin.js.parser.sourcemaps.*
|
||||
import org.jetbrains.kotlin.js.translate.context.Namer
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||
@@ -34,6 +36,7 @@ import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
||||
import org.jetbrains.kotlin.utils.JsLibraryUtils
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
import java.io.File
|
||||
import java.io.StringReader
|
||||
|
||||
// TODO: add hash checksum to defineModule?
|
||||
/**
|
||||
@@ -57,7 +60,14 @@ class FunctionReader(private val config: JsConfig, private val currentModuleName
|
||||
* kotlinVariable: kotlin object variable.
|
||||
* The default variable is Kotlin, but it can be renamed by minifier.
|
||||
*/
|
||||
data class ModuleInfo(val filePath: String, val fileContent: String, val moduleVariable: String, val kotlinVariable: String)
|
||||
class ModuleInfo(
|
||||
val filePath: String,
|
||||
val fileContent: String,
|
||||
val moduleVariable: String,
|
||||
val kotlinVariable: String,
|
||||
val offsetToSourceMapping: OffsetToSourceMapping,
|
||||
val sourceMap: SourceMap?
|
||||
)
|
||||
|
||||
private val moduleNameToInfo = HashMultimap.create<String, ModuleInfo>()
|
||||
|
||||
@@ -68,22 +78,40 @@ class FunctionReader(private val config: JsConfig, private val currentModuleName
|
||||
|
||||
moduleNameMap = buildModuleNameMap(fragments)
|
||||
|
||||
JsLibraryUtils.traverseJsLibraries(libs) { fileContent, filePath ->
|
||||
JsLibraryUtils.traverseJsLibraries(libs) { (content, path, sourceMapContent) ->
|
||||
var current = 0
|
||||
|
||||
while (true) {
|
||||
var index = fileContent.indexOf(DEFINE_MODULE_FIND_PATTERN, current)
|
||||
var index = content.indexOf(DEFINE_MODULE_FIND_PATTERN, current)
|
||||
if (index < 0) break
|
||||
|
||||
current = index + 1
|
||||
index = rewindToIdentifierStart(fileContent, index)
|
||||
val preciseMatcher = DEFINE_MODULE_PATTERN.matcher(offset(fileContent, index))
|
||||
index = rewindToIdentifierStart(content, index)
|
||||
val preciseMatcher = DEFINE_MODULE_PATTERN.matcher(offset(content, index))
|
||||
if (!preciseMatcher.lookingAt()) continue
|
||||
|
||||
val moduleName = preciseMatcher.group(3)
|
||||
val moduleVariable = preciseMatcher.group(4)
|
||||
val kotlinVariable = preciseMatcher.group(1)
|
||||
moduleNameToInfo.put(moduleName, ModuleInfo(filePath, fileContent, moduleVariable, kotlinVariable))
|
||||
|
||||
val sourceMap = sourceMapContent?.let {
|
||||
val result = SourceMapParser.parse(StringReader(it))
|
||||
when (result) {
|
||||
is SourceMapSuccess -> result.value
|
||||
is SourceMapError -> throw RuntimeException("Error parsing source map file: ${result.message}\n$it")
|
||||
}
|
||||
}
|
||||
|
||||
val moduleInfo = ModuleInfo(
|
||||
filePath = path,
|
||||
fileContent = content,
|
||||
moduleVariable = moduleVariable,
|
||||
kotlinVariable = kotlinVariable,
|
||||
offsetToSourceMapping = OffsetToSourceMapping(content),
|
||||
sourceMap = sourceMap
|
||||
)
|
||||
|
||||
moduleNameToInfo.put(moduleName, moduleInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,9 +180,16 @@ class FunctionReader(private val config: JsConfig, private val currentModuleName
|
||||
offset++
|
||||
}
|
||||
|
||||
val function = parseFunction(source, info.filePath, offset, ThrowExceptionOnErrorReporter, JsRootScope(JsProgram()))
|
||||
val position = info.offsetToSourceMapping[offset]
|
||||
val function = parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, JsRootScope(JsProgram()))
|
||||
val moduleReference = moduleNameMap[tag] ?: currentModuleName.makeRef()
|
||||
|
||||
val sourceMap = info.sourceMap
|
||||
if (sourceMap != null) {
|
||||
val remapper = SourceMapLocationRemapper(mapOf(info.filePath to sourceMap))
|
||||
remapper.remap(function)
|
||||
}
|
||||
|
||||
val replacements = hashMapOf(info.moduleVariable to moduleReference,
|
||||
info.kotlinVariable to Namer.kotlinObject())
|
||||
replaceExternalNames(function, replacements)
|
||||
|
||||
@@ -64,9 +64,10 @@ final class LineBuffer {
|
||||
*/
|
||||
static final int BUFLEN = 256;
|
||||
|
||||
LineBuffer(Reader in, int lineno) {
|
||||
LineBuffer(Reader in, CodePosition position) {
|
||||
this.in = in;
|
||||
this.lineno = lineno;
|
||||
this.lineno = position.getLine();
|
||||
this.lineStart = -position.getOffset();
|
||||
}
|
||||
|
||||
int read() throws IOException {
|
||||
|
||||
@@ -495,14 +495,15 @@ public class TokenStream {
|
||||
}
|
||||
|
||||
public TokenStream(Reader in,
|
||||
String sourceName, int lineno)
|
||||
String sourceName, CodePosition position)
|
||||
{
|
||||
this.in = new LineBuffer(in, lineno);
|
||||
this.in = new LineBuffer(in, position);
|
||||
this.pushbackToken = EOF;
|
||||
this.sourceName = sourceName;
|
||||
flags = 0;
|
||||
secondToLastPosition = new CodePosition(lineno, 0);
|
||||
lastPosition = new CodePosition(lineno, 0);
|
||||
secondToLastPosition = position;
|
||||
lastPosition = position;
|
||||
lastTokenPosition = position;
|
||||
}
|
||||
|
||||
/* return and pop the token from the stream if it matches...
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.parser
|
||||
|
||||
import com.google.gwt.dev.js.rhino.CodePosition
|
||||
|
||||
class OffsetToSourceMapping(text: String) {
|
||||
private val data: IntArray
|
||||
|
||||
init {
|
||||
val lineSeparators = LINE_SEPARATOR.findAll(text).map { it.range.endInclusive + 1 }
|
||||
data = (sequenceOf(0) + lineSeparators).toList().toIntArray()
|
||||
}
|
||||
|
||||
operator fun get(offset: Int): CodePosition {
|
||||
val lineNumber = data.binarySearch(offset).let { if (it >= 0) it else -it - 2 }
|
||||
return CodePosition(lineNumber, offset - data[lineNumber])
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val LINE_SEPARATOR = Regex("\\r\\n|\\r|\\n")
|
||||
}
|
||||
}
|
||||
@@ -16,29 +16,26 @@
|
||||
|
||||
package org.jetbrains.kotlin.js.parser
|
||||
|
||||
import com.google.gwt.dev.js.*
|
||||
import com.google.gwt.dev.js.JsAstMapper
|
||||
import com.google.gwt.dev.js.rhino.*
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsScope
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
||||
import org.jetbrains.kotlin.js.common.SourceInfoImpl
|
||||
import java.io.*
|
||||
import java.io.Reader
|
||||
import java.io.StringReader
|
||||
import java.util.*
|
||||
|
||||
private val FAKE_SOURCE_INFO = SourceInfoImpl(null, 0, 0, 0, 0)
|
||||
|
||||
fun parse(code: String, reporter: ErrorReporter, scope: JsScope, fileName: String): List<JsStatement> {
|
||||
val insideFunction = scope is JsFunctionScope
|
||||
val node = parse(code, 0, reporter, insideFunction, Parser::parse)
|
||||
val node = parse(code, CodePosition(0, 0), 0, reporter, insideFunction, Parser::parse)
|
||||
return node.toJsAst(scope, fileName) {
|
||||
mapStatements(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun parseFunction(code: String, fileName: String, offset: Int, reporter: ErrorReporter, scope: JsScope): JsFunction =
|
||||
parse(code, offset, reporter, insideFunction = false) {
|
||||
fun parseFunction(code: String, fileName: String, position: CodePosition, offset: Int, reporter: ErrorReporter, scope: JsScope): JsFunction =
|
||||
parse(code, position, offset, reporter, insideFunction = false) {
|
||||
addObserver(FunctionParsingObserver())
|
||||
primaryExpr(it)
|
||||
}.toJsAst(scope, fileName, JsAstMapper::mapFunction)
|
||||
@@ -65,6 +62,7 @@ private class FunctionParsingObserver : Observer {
|
||||
inline
|
||||
private fun parse(
|
||||
code: String,
|
||||
startPosition: CodePosition,
|
||||
offset: Int,
|
||||
reporter: ErrorReporter,
|
||||
insideFunction: Boolean,
|
||||
@@ -73,7 +71,7 @@ private fun parse(
|
||||
Context.enter().errorReporter = reporter
|
||||
|
||||
try {
|
||||
val ts = TokenStream(StringReader(code, offset), "<parser>", FAKE_SOURCE_INFO.line)
|
||||
val ts = TokenStream(StringReader(code, offset), "<parser>", startPosition)
|
||||
val parser = Parser(IRFactory(ts), insideFunction)
|
||||
return parser.parseAction(ts) as Node
|
||||
} finally {
|
||||
|
||||
@@ -39,61 +39,104 @@ import org.jetbrains.kotlin.js.test.utils.LineCollector
|
||||
import org.jetbrains.kotlin.js.test.utils.LineOutputToStringVisitor
|
||||
import org.jetbrains.kotlin.js.util.TextOutputImpl
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor
|
||||
import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
|
||||
import org.jetbrains.kotlin.utils.DFS
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.nio.charset.Charset
|
||||
|
||||
abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
|
||||
fun doTest(filePath: String) {
|
||||
val translator = K2JSTranslator(createConfig())
|
||||
val unit = TranslationUnit.SourceFile(createPsiFile(filePath))
|
||||
val translationResult = translator.translateUnits(listOf(unit), MainCallParameters.noCall())
|
||||
val file = File(filePath)
|
||||
val sourceCode = file.readText()
|
||||
|
||||
if (translationResult !is TranslationResult.Success) {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val collector = PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.PLAIN_FULL_PATHS, true)
|
||||
AnalyzerWithCompilerReport.reportDiagnostics(translationResult.diagnostics, collector)
|
||||
val messages = outputStream.toByteArray().toString(Charset.forName("UTF-8"))
|
||||
throw AssertionError("The following errors occurred compiling test:\n" + messages)
|
||||
TestFileFactoryImpl().use { testFactory ->
|
||||
val inputFiles = KotlinTestUtils.createTestFiles(file.name, sourceCode, testFactory, true)
|
||||
val modules = inputFiles
|
||||
.map { it.module }.distinct()
|
||||
.associateBy { it.name }
|
||||
|
||||
val orderedModules = DFS.topologicalOrder(modules.values) { module -> module.dependencies.mapNotNull { modules[it] } }
|
||||
|
||||
orderedModules.asReversed().forEach { module ->
|
||||
val baseOutputPath = module.outputFileName(file)
|
||||
|
||||
val translator = K2JSTranslator(createConfig(module, file, modules))
|
||||
val units = module.files.map { TranslationUnit.SourceFile(createPsiFile(it.fileName)) }
|
||||
val translationResult = translator.translateUnits(units, MainCallParameters.noCall())
|
||||
|
||||
if (translationResult !is TranslationResult.Success) {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val collector = PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.PLAIN_FULL_PATHS, true)
|
||||
AnalyzerWithCompilerReport.reportDiagnostics(translationResult.diagnostics, collector)
|
||||
val messages = outputStream.toByteArray().toString(Charset.forName("UTF-8"))
|
||||
throw AssertionError("The following errors occurred compiling test:\n" + messages)
|
||||
}
|
||||
|
||||
val lineCollector = LineCollector()
|
||||
lineCollector.accept(translationResult.program)
|
||||
|
||||
val programOutput = TextOutputImpl()
|
||||
translationResult.program.globalBlock.accept(LineOutputToStringVisitor(programOutput, lineCollector))
|
||||
val generatedCode = programOutput.toString()
|
||||
with(File(baseOutputPath + "-lines.js")) {
|
||||
parentFile.mkdirs()
|
||||
writeText(generatedCode)
|
||||
}
|
||||
|
||||
File(baseOutputPath + ".js").writeText(translationResult.program.globalBlock.toString())
|
||||
|
||||
val moduleDescription = JsModuleDescriptor(
|
||||
name = module.name,
|
||||
data = translationResult.moduleDescriptor,
|
||||
kind = ModuleKind.PLAIN,
|
||||
imported = emptyList()
|
||||
)
|
||||
val metaFileContent = KotlinJavascriptSerializationUtil.metadataAsString(
|
||||
translationResult.bindingContext, moduleDescription)
|
||||
File(baseOutputPath + ".meta.js").writeText(metaFileContent)
|
||||
|
||||
val linesMatcher = module.files
|
||||
.mapNotNull { LINES_PATTERN.find(File(it.fileName).readText()) }
|
||||
.firstOrNull()
|
||||
?: error("'// LINES: ' comment was not found in source file. Generated code is:\n$generatedCode")
|
||||
|
||||
val expectedLines = linesMatcher.groups[1]!!.value
|
||||
val actualLines = lineCollector.lines
|
||||
.dropLastWhile { it == null }
|
||||
.joinToString(" ") { if (it == null) "*" else (it + 1).toString() }
|
||||
|
||||
TestCase.assertEquals(generatedCode, expectedLines, actualLines)
|
||||
}
|
||||
}
|
||||
|
||||
val lineCollector = LineCollector()
|
||||
lineCollector.accept(translationResult.program)
|
||||
|
||||
val programOutput = TextOutputImpl()
|
||||
translationResult.program.globalBlock.accept(LineOutputToStringVisitor(programOutput, lineCollector))
|
||||
val generatedCode = programOutput.toString()
|
||||
val relativePath = File(filePath).relativeTo(File(BASE_PATH)).path.removeSuffix(".kt")
|
||||
with(File(File(OUT_PATH), relativePath + ".js")) {
|
||||
parentFile.mkdirs()
|
||||
writeText(generatedCode)
|
||||
}
|
||||
|
||||
val sourceCode = FileUtil.loadFile(File(filePath))
|
||||
val linesMatcher = LINES_PATTERN.find(sourceCode) ?:
|
||||
error("'// LINES: ' comment was not found in source file. Generated code is:\n$generatedCode")
|
||||
|
||||
val expectedLines = linesMatcher.groups[1]!!.value
|
||||
val actualLines = lineCollector.lines
|
||||
.dropLastWhile { it == null }
|
||||
.joinToString(" ") { if (it == null) "*" else (it + 1).toString() }
|
||||
|
||||
TestCase.assertEquals(generatedCode, expectedLines, actualLines)
|
||||
}
|
||||
|
||||
private fun TestModule.outputFileName(file: File): String {
|
||||
return outputPath(file) + "-" + name
|
||||
}
|
||||
|
||||
private fun outputPath(file: File) = File(OUT_PATH, file.relativeTo(File(BASE_PATH)).path.removeSuffix(".kt")).path
|
||||
|
||||
override fun createEnvironment(): KotlinCoreEnvironment {
|
||||
return KotlinCoreEnvironment.createForTests(testRootDisposable, CompilerConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES)
|
||||
}
|
||||
|
||||
private fun createConfig(): JsConfig {
|
||||
private fun createConfig(module: TestModule, inputFile: File, modules: Map<String, TestModule>): JsConfig {
|
||||
val dependencies = module.dependencies
|
||||
.mapNotNull { modules[it]?.outputFileName(inputFile) }
|
||||
.map { "$it.meta.js" }
|
||||
|
||||
val configuration = environment.configuration.copy()
|
||||
|
||||
configuration.put(JSConfigurationKeys.LIBRARIES, JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST)
|
||||
configuration.put(JSConfigurationKeys.LIBRARIES, JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST + dependencies )
|
||||
|
||||
configuration.put(CommonConfigurationKeys.MODULE_NAME, "test")
|
||||
configuration.put(CommonConfigurationKeys.MODULE_NAME, module.name)
|
||||
configuration.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
|
||||
configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5)
|
||||
|
||||
@@ -109,6 +152,44 @@ abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
|
||||
return psiManager.findFile(fileSystem.findFileByPath(fileName)!!) as KtFile
|
||||
}
|
||||
|
||||
private inner class TestFileFactoryImpl : KotlinTestUtils.TestFileFactory<TestModule, TestFile>, Closeable {
|
||||
val tmpDir = KotlinTestUtils.tmpDir("js-tests")
|
||||
val defaultModule = TestModule(BasicBoxTest.TEST_MODULE, emptyList())
|
||||
|
||||
override fun createFile(module: TestModule?, fileName: String, text: String, directives: Map<String, String>): TestFile? {
|
||||
val currentModule = module ?: defaultModule
|
||||
|
||||
val temporaryFile = File(tmpDir, "${currentModule.name}/$fileName")
|
||||
KotlinTestUtils.mkdirs(temporaryFile.parentFile)
|
||||
temporaryFile.writeText(text, Charsets.UTF_8)
|
||||
|
||||
|
||||
return TestFile(temporaryFile.absolutePath, currentModule)
|
||||
}
|
||||
|
||||
override fun createModule(name: String, dependencies: List<String>, friends: List<String>): TestModule? {
|
||||
return TestModule(name, dependencies)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
FileUtil.delete(tmpDir)
|
||||
}
|
||||
}
|
||||
|
||||
private class TestModule(
|
||||
val name: String,
|
||||
dependencies: List<String>
|
||||
) {
|
||||
val dependencies = dependencies.toMutableList()
|
||||
val files = mutableListOf<TestFile>()
|
||||
}
|
||||
|
||||
private class TestFile(val fileName: String, val module: TestModule) {
|
||||
init {
|
||||
module.files += this
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIR_NAME = "lineNumbers"
|
||||
private val LINES_PATTERN = Regex("^ *// *LINES: *(.*)$", RegexOption.MULTILINE)
|
||||
|
||||
@@ -87,9 +87,7 @@ abstract class BasicBoxTest(
|
||||
val outputPostfixFile = getOutputPostfixFile(filePath)
|
||||
|
||||
TestFileFactoryImpl().use { testFactory ->
|
||||
testFactory.defaultModule.moduleKind
|
||||
|
||||
val inputFiles = KotlinTestUtils.createTestFiles(file.name, fileContent, testFactory)
|
||||
val inputFiles = KotlinTestUtils.createTestFiles(file.name, fileContent, testFactory, true)
|
||||
val modules = inputFiles
|
||||
.map { it.module }.distinct()
|
||||
.map { it.name to it }.toMap()
|
||||
@@ -627,7 +625,7 @@ abstract class BasicBoxTest(
|
||||
private val METADATA_EXTENSION = "jsmeta"
|
||||
private val HEADER_FILE = "header.$METADATA_EXTENSION"
|
||||
|
||||
private val TEST_MODULE = "JS_TESTS"
|
||||
val TEST_MODULE = "JS_TESTS"
|
||||
private val DEFAULT_MODULE = "main"
|
||||
private val TEST_FUNCTION = "box"
|
||||
private val OLD_MODULE_SUFFIX = "-old"
|
||||
|
||||
@@ -269,4 +269,19 @@ public class JsLineNumberTestGenerated extends AbstractJsLineNumberTest {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/lineNumbers/whileWithComplexCondition.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("js/js.translator/testData/lineNumbers/inlineMultiModule")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class InlineMultiModule extends AbstractJsLineNumberTest {
|
||||
public void testAllFilesPresentInInlineMultiModule() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/lineNumbers/inlineMultiModule"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/lineNumbers/inlineMultiModule/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ abstract class TranslationResult protected constructor(val diagnostics: Diagnost
|
||||
val program: JsProgram,
|
||||
diagnostics: Diagnostics,
|
||||
private val importedModules: List<String>,
|
||||
private val moduleDescriptor: ModuleDescriptor,
|
||||
private val bindingContext: BindingContext,
|
||||
val moduleDescriptor: ModuleDescriptor,
|
||||
val bindingContext: BindingContext,
|
||||
val metadataHeader: ByteArray?,
|
||||
val fileTranslationResults: Map<KtFile, FileTranslationResult>
|
||||
) : TranslationResult(diagnostics) {
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
class A {
|
||||
val z by
|
||||
lazy { 23 }
|
||||
Delegate { 23 }
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
println(A().z)
|
||||
}
|
||||
|
||||
// LINES: 2 3 * 2 * 3 * 7
|
||||
class Delegate(val f: () -> Int) {
|
||||
operator fun getValue(thisRef: Any?, property: Any): Int {
|
||||
return f()
|
||||
}
|
||||
}
|
||||
|
||||
// LINES: 2 3 2 * 3 * 7 10 12
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// MODULE: lib
|
||||
// FILE: lib.kt
|
||||
|
||||
package pkg1
|
||||
|
||||
inline fun foo(x: String) {
|
||||
println("foo1($x);")
|
||||
println("foo2($x);")
|
||||
}
|
||||
|
||||
// LINES: 7 8
|
||||
|
||||
// MODULE: main(lib)
|
||||
// FILE: main.kt
|
||||
|
||||
package pkg2
|
||||
import pkg1.*
|
||||
|
||||
fun box() {
|
||||
foo("23")
|
||||
foo("42")
|
||||
}
|
||||
|
||||
// LINES: 7 20 7 8 20 8 7 21 7 8 21 8
|
||||
Reference in New Issue
Block a user