Use source map remapper in JS inliner

This commit is contained in:
Alexey Andreev
2017-05-23 19:50:38 +03:00
parent 9c4ec902b0
commit bf21cfd6e0
14 changed files with 344 additions and 89 deletions
@@ -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) {
+8 -2
View File
@@ -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