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*MODULE:\\s*([^()\\n]+)(?:\\(([^()]+(?:" + MODULE_DELIMITER + "[^()]+)*)\\))?\\s*(?:\\(([^()]+(?:" + MODULE_DELIMITER + "[^()]+)*)\\))?\\s*)?" +
|
||||||
"//\\s*FILE:\\s*(.*)$", Pattern.MULTILINE);
|
"//\\s*FILE:\\s*(.*)$", Pattern.MULTILINE);
|
||||||
private static final Pattern DIRECTIVE_PATTERN = Pattern.compile("^//\\s*!([\\w_]+)(:\\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() {
|
public static final BindingTrace DUMMY_TRACE = new BindingTrace() {
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -648,6 +649,12 @@ public class KotlinTestUtils {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static <M, F> List<F> createTestFiles(String testFileName, String expectedText, TestFileFactory<M, F> factory) {
|
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);
|
Map<String, String> directives = parseDirectives(expectedText);
|
||||||
|
|
||||||
List<F> testFiles = Lists.newArrayList();
|
List<F> testFiles = Lists.newArrayList();
|
||||||
@@ -681,7 +688,9 @@ public class KotlinTestUtils {
|
|||||||
else {
|
else {
|
||||||
end = expectedText.length();
|
end = expectedText.length();
|
||||||
}
|
}
|
||||||
String fileText = expectedText.substring(start, end);
|
String fileText = preserveLocations ?
|
||||||
|
substringKeepingLocations(expectedText, start, end) :
|
||||||
|
expectedText.substring(start,end);
|
||||||
processedChars = end;
|
processedChars = end;
|
||||||
|
|
||||||
testFiles.add(factory.createFile(module, fileName, fileText, directives));
|
testFiles.add(factory.createFile(module, fileName, fileText, directives));
|
||||||
@@ -730,6 +739,26 @@ public class KotlinTestUtils {
|
|||||||
return testFiles;
|
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) {
|
private static List<String> parseModuleList(@Nullable String dependencies) {
|
||||||
if (dependencies == null) return Collections.emptyList();
|
if (dependencies == null) return Collections.emptyList();
|
||||||
return StringsKt.split(dependencies, Pattern.compile(MODULE_DELIMITER), 0);
|
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 org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
object JsLibraryUtils {
|
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) }
|
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 {
|
when {
|
||||||
lib.isDirectory -> traverseDirectory(lib, action)
|
lib.isDirectory -> traverseDirectory(lib, action)
|
||||||
FileUtil.isJarOrZip(lib) -> traverseArchive(lib, action)
|
FileUtil.isJarOrZip(lib) -> traverseArchive(lib, action)
|
||||||
lib.name.endsWith(KotlinJavascriptMetadataUtils.JS_EXT) -> {
|
lib.name.endsWith(KotlinJavascriptMetadataUtils.JS_EXT) -> {
|
||||||
lib.runIfFileExists(action)
|
lib.runIfFileExists(lib.path, action)
|
||||||
val jsFile = lib.withReplacedExtensionOrNull(
|
val jsFile = lib.withReplacedExtensionOrNull(
|
||||||
KotlinJavascriptMetadataUtils.META_JS_SUFFIX, KotlinJavascriptMetadataUtils.JS_EXT
|
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) {
|
if (isFile) {
|
||||||
action(FileUtil.loadFile(this), "")
|
action(JsLibrary(readText(), relativePath, correspondingSourceMapFile().contentIfExists()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyJsFilesFromDirectory(dir: File, outputLibraryJsPath: String) {
|
private fun copyJsFilesFromDirectory(dir: File, outputLibraryJsPath: String) {
|
||||||
traverseDirectory(dir) { content, relativePath ->
|
traverseDirectory(dir) { (content, path) ->
|
||||||
FileUtil.writeToFile(File(outputLibraryJsPath, relativePath), content)
|
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 ->
|
FileUtil.processFilesRecursively(dir, Processor<File> { file ->
|
||||||
val relativePath = FileUtil.getRelativePath(dir, file)
|
val relativePath = FileUtil.getRelativePath(dir, file)
|
||||||
?: throw IllegalArgumentException("relativePath should not be null $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
|
val suggestedRelativePath = getSuggestedPath(relativePath) ?: return@Processor true
|
||||||
action(FileUtil.loadFile(file), suggestedRelativePath)
|
file.runIfFileExists(suggestedRelativePath, action)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun traverseDirectory(dir: File, action: (content: String, relativePath: String) -> Unit) {
|
private fun traverseDirectory(dir: File, action: (JsLibrary) -> Unit) {
|
||||||
try {
|
try {
|
||||||
processDirectory(dir, action)
|
processDirectory(dir, action)
|
||||||
}
|
}
|
||||||
@@ -95,27 +100,49 @@ object JsLibraryUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyJsFilesFromZip(file: File, outputLibraryJsPath: String) {
|
private fun copyJsFilesFromZip(file: File, outputLibraryJsPath: String) {
|
||||||
traverseArchive(file) { content, relativePath ->
|
traverseArchive(file) { library ->
|
||||||
FileUtil.writeToFile(File(outputLibraryJsPath, relativePath), content)
|
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)
|
val zipFile = ZipFile(file.path)
|
||||||
try {
|
try {
|
||||||
val zipEntries = zipFile.entries()
|
val zipEntries = zipFile.entries()
|
||||||
|
val librariesWithoutSourceMaps = mutableListOf<JsLibrary>()
|
||||||
|
val possibleMapFiles = mutableMapOf<String, ZipEntry>()
|
||||||
|
|
||||||
while (zipEntries.hasMoreElements()) {
|
while (zipEntries.hasMoreElements()) {
|
||||||
val entry = zipEntries.nextElement()
|
val entry = zipEntries.nextElement()
|
||||||
val entryName = entry.name
|
val entryName = entry.name
|
||||||
if (!entry.isDirectory && entryName.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
|
if (!entry.isDirectory) {
|
||||||
|
if (entryName.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
|
||||||
val relativePath = getSuggestedPath(entryName) ?: continue
|
val relativePath = getSuggestedPath(entryName) ?: continue
|
||||||
|
|
||||||
val stream = zipFile.getInputStream(entry)
|
val stream = zipFile.getInputStream(entry)
|
||||||
val content = FileUtil.loadTextAndClose(stream)
|
val content = FileUtil.loadTextAndClose(stream)
|
||||||
action(content, relativePath)
|
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) {
|
catch (ex: IOException) {
|
||||||
LOG.error("Could not extract files from archive ${file.name}: ${ex.message}")
|
LOG.error("Could not extract files from archive ${file.name}: ${ex.message}")
|
||||||
}
|
}
|
||||||
@@ -136,3 +163,5 @@ object JsLibraryUtils {
|
|||||||
return path
|
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 {
|
object KotlinJavascriptMetadataUtils {
|
||||||
const val JS_EXT: String = ".js"
|
const val JS_EXT: String = ".js"
|
||||||
const val META_JS_SUFFIX: String = ".meta.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 = "kotlin_module_metadata"
|
||||||
private val KOTLIN_JAVASCRIPT_METHOD_NAME_PATTERN = "\\.kotlin_module_metadata\\(".toPattern()
|
private val KOTLIN_JAVASCRIPT_METHOD_NAME_PATTERN = "\\.kotlin_module_metadata\\(".toPattern()
|
||||||
|
|
||||||
@@ -78,8 +79,8 @@ object KotlinJavascriptMetadataUtils {
|
|||||||
fun loadMetadata(file: File): List<KotlinJavascriptMetadata> {
|
fun loadMetadata(file: File): List<KotlinJavascriptMetadata> {
|
||||||
assert(file.exists()) { "Library $file not found" }
|
assert(file.exists()) { "Library $file not found" }
|
||||||
val metadataList = arrayListOf<KotlinJavascriptMetadata>()
|
val metadataList = arrayListOf<KotlinJavascriptMetadata>()
|
||||||
JsLibraryUtils.traverseJsLibrary(file) { content, _ ->
|
JsLibraryUtils.traverseJsLibrary(file) { library ->
|
||||||
parseMetadata(content, metadataList)
|
parseMetadata(library.content, metadataList)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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.config.JsConfig
|
||||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
||||||
import org.jetbrains.kotlin.js.inline.util.isCallInvocation
|
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.parseFunction
|
||||||
|
import org.jetbrains.kotlin.js.parser.sourcemaps.*
|
||||||
import org.jetbrains.kotlin.js.translate.context.Namer
|
import org.jetbrains.kotlin.js.translate.context.Namer
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName
|
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
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.JsLibraryUtils
|
||||||
import org.jetbrains.kotlin.utils.sure
|
import org.jetbrains.kotlin.utils.sure
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.StringReader
|
||||||
|
|
||||||
// TODO: add hash checksum to defineModule?
|
// TODO: add hash checksum to defineModule?
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +60,14 @@ class FunctionReader(private val config: JsConfig, private val currentModuleName
|
|||||||
* kotlinVariable: kotlin object variable.
|
* kotlinVariable: kotlin object variable.
|
||||||
* The default variable is Kotlin, but it can be renamed by minifier.
|
* 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>()
|
private val moduleNameToInfo = HashMultimap.create<String, ModuleInfo>()
|
||||||
|
|
||||||
@@ -68,22 +78,40 @@ class FunctionReader(private val config: JsConfig, private val currentModuleName
|
|||||||
|
|
||||||
moduleNameMap = buildModuleNameMap(fragments)
|
moduleNameMap = buildModuleNameMap(fragments)
|
||||||
|
|
||||||
JsLibraryUtils.traverseJsLibraries(libs) { fileContent, filePath ->
|
JsLibraryUtils.traverseJsLibraries(libs) { (content, path, sourceMapContent) ->
|
||||||
var current = 0
|
var current = 0
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var index = fileContent.indexOf(DEFINE_MODULE_FIND_PATTERN, current)
|
var index = content.indexOf(DEFINE_MODULE_FIND_PATTERN, current)
|
||||||
if (index < 0) break
|
if (index < 0) break
|
||||||
|
|
||||||
current = index + 1
|
current = index + 1
|
||||||
index = rewindToIdentifierStart(fileContent, index)
|
index = rewindToIdentifierStart(content, index)
|
||||||
val preciseMatcher = DEFINE_MODULE_PATTERN.matcher(offset(fileContent, index))
|
val preciseMatcher = DEFINE_MODULE_PATTERN.matcher(offset(content, index))
|
||||||
if (!preciseMatcher.lookingAt()) continue
|
if (!preciseMatcher.lookingAt()) continue
|
||||||
|
|
||||||
val moduleName = preciseMatcher.group(3)
|
val moduleName = preciseMatcher.group(3)
|
||||||
val moduleVariable = preciseMatcher.group(4)
|
val moduleVariable = preciseMatcher.group(4)
|
||||||
val kotlinVariable = preciseMatcher.group(1)
|
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++
|
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 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,
|
val replacements = hashMapOf(info.moduleVariable to moduleReference,
|
||||||
info.kotlinVariable to Namer.kotlinObject())
|
info.kotlinVariable to Namer.kotlinObject())
|
||||||
replaceExternalNames(function, replacements)
|
replaceExternalNames(function, replacements)
|
||||||
|
|||||||
@@ -64,9 +64,10 @@ final class LineBuffer {
|
|||||||
*/
|
*/
|
||||||
static final int BUFLEN = 256;
|
static final int BUFLEN = 256;
|
||||||
|
|
||||||
LineBuffer(Reader in, int lineno) {
|
LineBuffer(Reader in, CodePosition position) {
|
||||||
this.in = in;
|
this.in = in;
|
||||||
this.lineno = lineno;
|
this.lineno = position.getLine();
|
||||||
|
this.lineStart = -position.getOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
int read() throws IOException {
|
int read() throws IOException {
|
||||||
|
|||||||
@@ -495,14 +495,15 @@ public class TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TokenStream(Reader in,
|
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.pushbackToken = EOF;
|
||||||
this.sourceName = sourceName;
|
this.sourceName = sourceName;
|
||||||
flags = 0;
|
flags = 0;
|
||||||
secondToLastPosition = new CodePosition(lineno, 0);
|
secondToLastPosition = position;
|
||||||
lastPosition = new CodePosition(lineno, 0);
|
lastPosition = position;
|
||||||
|
lastTokenPosition = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* return and pop the token from the stream if it matches...
|
/* 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
|
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 com.google.gwt.dev.js.rhino.*
|
||||||
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope
|
import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsScope
|
import org.jetbrains.kotlin.js.backend.ast.JsScope
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
||||||
import org.jetbrains.kotlin.js.common.SourceInfoImpl
|
import java.io.Reader
|
||||||
import java.io.*
|
import java.io.StringReader
|
||||||
import java.util.*
|
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> {
|
fun parse(code: String, reporter: ErrorReporter, scope: JsScope, fileName: String): List<JsStatement> {
|
||||||
val insideFunction = scope is JsFunctionScope
|
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) {
|
return node.toJsAst(scope, fileName) {
|
||||||
mapStatements(it)
|
mapStatements(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseFunction(code: String, fileName: String, offset: Int, reporter: ErrorReporter, scope: JsScope): JsFunction =
|
fun parseFunction(code: String, fileName: String, position: CodePosition, offset: Int, reporter: ErrorReporter, scope: JsScope): JsFunction =
|
||||||
parse(code, offset, reporter, insideFunction = false) {
|
parse(code, position, offset, reporter, insideFunction = false) {
|
||||||
addObserver(FunctionParsingObserver())
|
addObserver(FunctionParsingObserver())
|
||||||
primaryExpr(it)
|
primaryExpr(it)
|
||||||
}.toJsAst(scope, fileName, JsAstMapper::mapFunction)
|
}.toJsAst(scope, fileName, JsAstMapper::mapFunction)
|
||||||
@@ -65,6 +62,7 @@ private class FunctionParsingObserver : Observer {
|
|||||||
inline
|
inline
|
||||||
private fun parse(
|
private fun parse(
|
||||||
code: String,
|
code: String,
|
||||||
|
startPosition: CodePosition,
|
||||||
offset: Int,
|
offset: Int,
|
||||||
reporter: ErrorReporter,
|
reporter: ErrorReporter,
|
||||||
insideFunction: Boolean,
|
insideFunction: Boolean,
|
||||||
@@ -73,7 +71,7 @@ private fun parse(
|
|||||||
Context.enter().errorReporter = reporter
|
Context.enter().errorReporter = reporter
|
||||||
|
|
||||||
try {
|
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)
|
val parser = Parser(IRFactory(ts), insideFunction)
|
||||||
return parser.parseAction(ts) as Node
|
return parser.parseAction(ts) as Node
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -39,18 +39,37 @@ import org.jetbrains.kotlin.js.test.utils.LineCollector
|
|||||||
import org.jetbrains.kotlin.js.test.utils.LineOutputToStringVisitor
|
import org.jetbrains.kotlin.js.test.utils.LineOutputToStringVisitor
|
||||||
import org.jetbrains.kotlin.js.util.TextOutputImpl
|
import org.jetbrains.kotlin.js.util.TextOutputImpl
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
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.serialization.js.ModuleKind
|
||||||
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
|
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
|
||||||
|
import org.jetbrains.kotlin.utils.DFS
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
|
abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
|
||||||
fun doTest(filePath: String) {
|
fun doTest(filePath: String) {
|
||||||
val translator = K2JSTranslator(createConfig())
|
val file = File(filePath)
|
||||||
val unit = TranslationUnit.SourceFile(createPsiFile(filePath))
|
val sourceCode = file.readText()
|
||||||
val translationResult = translator.translateUnits(listOf(unit), MainCallParameters.noCall())
|
|
||||||
|
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) {
|
if (translationResult !is TranslationResult.Success) {
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
@@ -66,15 +85,27 @@ abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
|
|||||||
val programOutput = TextOutputImpl()
|
val programOutput = TextOutputImpl()
|
||||||
translationResult.program.globalBlock.accept(LineOutputToStringVisitor(programOutput, lineCollector))
|
translationResult.program.globalBlock.accept(LineOutputToStringVisitor(programOutput, lineCollector))
|
||||||
val generatedCode = programOutput.toString()
|
val generatedCode = programOutput.toString()
|
||||||
val relativePath = File(filePath).relativeTo(File(BASE_PATH)).path.removeSuffix(".kt")
|
with(File(baseOutputPath + "-lines.js")) {
|
||||||
with(File(File(OUT_PATH), relativePath + ".js")) {
|
|
||||||
parentFile.mkdirs()
|
parentFile.mkdirs()
|
||||||
writeText(generatedCode)
|
writeText(generatedCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourceCode = FileUtil.loadFile(File(filePath))
|
File(baseOutputPath + ".js").writeText(translationResult.program.globalBlock.toString())
|
||||||
val linesMatcher = LINES_PATTERN.find(sourceCode) ?:
|
|
||||||
error("'// LINES: ' comment was not found in source file. Generated code is:\n$generatedCode")
|
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 expectedLines = linesMatcher.groups[1]!!.value
|
||||||
val actualLines = lineCollector.lines
|
val actualLines = lineCollector.lines
|
||||||
@@ -83,17 +114,29 @@ abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
|
|||||||
|
|
||||||
TestCase.assertEquals(generatedCode, expectedLines, actualLines)
|
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 {
|
override fun createEnvironment(): KotlinCoreEnvironment {
|
||||||
return KotlinCoreEnvironment.createForTests(testRootDisposable, CompilerConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES)
|
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()
|
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.MODULE_KIND, ModuleKind.PLAIN)
|
||||||
configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5)
|
configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5)
|
||||||
|
|
||||||
@@ -109,6 +152,44 @@ abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() {
|
|||||||
return psiManager.findFile(fileSystem.findFileByPath(fileName)!!) as KtFile
|
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 {
|
companion object {
|
||||||
private val DIR_NAME = "lineNumbers"
|
private val DIR_NAME = "lineNumbers"
|
||||||
private val LINES_PATTERN = Regex("^ *// *LINES: *(.*)$", RegexOption.MULTILINE)
|
private val LINES_PATTERN = Regex("^ *// *LINES: *(.*)$", RegexOption.MULTILINE)
|
||||||
|
|||||||
@@ -87,9 +87,7 @@ abstract class BasicBoxTest(
|
|||||||
val outputPostfixFile = getOutputPostfixFile(filePath)
|
val outputPostfixFile = getOutputPostfixFile(filePath)
|
||||||
|
|
||||||
TestFileFactoryImpl().use { testFactory ->
|
TestFileFactoryImpl().use { testFactory ->
|
||||||
testFactory.defaultModule.moduleKind
|
val inputFiles = KotlinTestUtils.createTestFiles(file.name, fileContent, testFactory, true)
|
||||||
|
|
||||||
val inputFiles = KotlinTestUtils.createTestFiles(file.name, fileContent, testFactory)
|
|
||||||
val modules = inputFiles
|
val modules = inputFiles
|
||||||
.map { it.module }.distinct()
|
.map { it.module }.distinct()
|
||||||
.map { it.name to it }.toMap()
|
.map { it.name to it }.toMap()
|
||||||
@@ -627,7 +625,7 @@ abstract class BasicBoxTest(
|
|||||||
private val METADATA_EXTENSION = "jsmeta"
|
private val METADATA_EXTENSION = "jsmeta"
|
||||||
private val HEADER_FILE = "header.$METADATA_EXTENSION"
|
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 DEFAULT_MODULE = "main"
|
||||||
private val TEST_FUNCTION = "box"
|
private val TEST_FUNCTION = "box"
|
||||||
private val OLD_MODULE_SUFFIX = "-old"
|
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");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/lineNumbers/whileWithComplexCondition.kt");
|
||||||
doTest(fileName);
|
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,
|
val program: JsProgram,
|
||||||
diagnostics: Diagnostics,
|
diagnostics: Diagnostics,
|
||||||
private val importedModules: List<String>,
|
private val importedModules: List<String>,
|
||||||
private val moduleDescriptor: ModuleDescriptor,
|
val moduleDescriptor: ModuleDescriptor,
|
||||||
private val bindingContext: BindingContext,
|
val bindingContext: BindingContext,
|
||||||
val metadataHeader: ByteArray?,
|
val metadataHeader: ByteArray?,
|
||||||
val fileTranslationResults: Map<KtFile, FileTranslationResult>
|
val fileTranslationResults: Map<KtFile, FileTranslationResult>
|
||||||
) : TranslationResult(diagnostics) {
|
) : TranslationResult(diagnostics) {
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
class A {
|
class A {
|
||||||
val z by
|
val z by
|
||||||
lazy { 23 }
|
Delegate { 23 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
println(A().z)
|
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