diff --git a/.idea/libraries/json_org.xml b/.idea/libraries/json_org.xml new file mode 100644 index 00000000000..37a4912e679 --- /dev/null +++ b/.idea/libraries/json_org.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/build.xml b/build.xml index 44f24010bef..e4db33970dc 100644 --- a/build.xml +++ b/build.xml @@ -65,6 +65,7 @@ + @@ -385,6 +386,7 @@ + diff --git a/js/js.parser/js.parser.iml b/js/js.parser/js.parser.iml index 9add1fd0a16..8066bb12c1a 100644 --- a/js/js.parser/js.parser.iml +++ b/js/js.parser/js.parser.iml @@ -10,5 +10,6 @@ + \ No newline at end of file diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt new file mode 100644 index 00000000000..e19e90834a5 --- /dev/null +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt @@ -0,0 +1,38 @@ +/* + * 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.sourcemaps + +class SourceMap { + val groups = mutableListOf() +} + +class SourceMapSegment( + val generatedColumnNumber: Int, + val sourceFileName: String, + val sourceLineNumber: Int, + val sourceColumnNumber: Int +) + +class SourceMapGroup { + val segments = mutableListOf() +} + +sealed class SourceMapParseResult + +class SourceMapSuccess(val value: SourceMap) : SourceMapParseResult() + +class SourceMapError(val message: String) : SourceMapParseResult() \ No newline at end of file diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt new file mode 100644 index 00000000000..5adf792e1ce --- /dev/null +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt @@ -0,0 +1,70 @@ +/* + * 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.sourcemaps + +import org.jetbrains.kotlin.js.backend.ast.JsLocation +import org.jetbrains.kotlin.js.backend.ast.JsNode +import org.jetbrains.kotlin.js.backend.ast.RecursiveJsVisitor +import org.jetbrains.kotlin.js.backend.ast.SourceInfoAwareJsNode + +class SourceMapLocationRemapper(val sourceMaps: Map) { + fun remap(node: JsNode) { + node.accept(visitor) + } + + private val visitor = object : RecursiveJsVisitor() { + private var lastSourceMap: SourceMap? = null + private var lastGroup: SourceMapGroup? = null + private var lastSegmentIndex = 0 + + override fun visitElement(node: JsNode) { + if (node is SourceInfoAwareJsNode) { + if (!remapNode(node)) { + node.source = null + } + } + super.visitElement(node) + } + + private fun remapNode(node: SourceInfoAwareJsNode): Boolean { + val source = node.source as? JsLocation ?: return false + + val sourceMap = sourceMaps[source.file] ?: return false + val group = sourceMap.groups.getOrElse(source.startLine) { return false } + if (group.segments.isEmpty()) return false + + if (lastSourceMap != sourceMap || lastGroup != group) { + lastSegmentIndex = 0 + } + if (group.segments[lastSegmentIndex].generatedColumnNumber > source.startChar) { + if (lastSegmentIndex == 0) return false + lastSegmentIndex = 0 + } + + while (lastSegmentIndex + 1 < group.segments.size) { + val nextIndex = lastSegmentIndex + 1 + if (group.segments[nextIndex].generatedColumnNumber > source.startChar) break + lastSegmentIndex = nextIndex + } + + val segment = group.segments[lastSegmentIndex] + node.source = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber) + + return true + } + } +} \ No newline at end of file diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt new file mode 100644 index 00000000000..65323b064f1 --- /dev/null +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapParser.kt @@ -0,0 +1,169 @@ +/* + * 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. + */ + +/* + * 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.sourcemaps + +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.json.JSONTokener +import java.io.IOException +import java.io.Reader + +object SourceMapParser { + @Throws(IOException::class) + fun parse(reader: Reader): SourceMapParseResult { + val jsonObject = try { + JSONObject(JSONTokener(reader)) + } + catch (e: JSONException) { + return SourceMapError(e.message ?: "parse error") + } + + if (!jsonObject.has("version")) return SourceMapError("Version not defined") + jsonObject.get("version").let { + if (it != 3) return SourceMapError("Unsupported version: $it") + } + + val sourceRoot = if (jsonObject.has("sourceRoot")) { + jsonObject.get("sourceRoot") as? String ?: return SourceMapError("'sourceRoot' property is not of string type") + } + else { + "" + } + + val sources = if (jsonObject.has("sources")) { + val sourcesProperty = jsonObject.get("sources") as? JSONArray ?: + return SourceMapError("'sources' property is not of array type") + sourcesProperty.map { + it as? String ?: return SourceMapError("'sources' array must contain strings") + } + } + else { + emptyList() + } + + if (!jsonObject.has("mappings")) return SourceMapError("'mappings' property not found") + val mappings = jsonObject.get("mappings") as? String ?: return SourceMapError("'mappings' property is not of string type") + + var jsColumn = 0 + var sourceLine = 0 + var sourceColumn = 0 + var sourceIndex = 0 + val stream = MappingStream(mappings) + val sourceMap = SourceMap() + var currentGroup = SourceMapGroup().also { sourceMap.groups += it } + + while (!stream.isEof) { + if (stream.isGroupTerminator) { + currentGroup = SourceMapGroup().also { sourceMap.groups += it } + jsColumn = 0 + stream.skipChar() + continue + } + + jsColumn += stream.readInt() ?: return stream.createError("VLQ-encoded JS column number expected") + + if (stream.isEncodedInt) { + sourceIndex += stream.readInt() ?: return stream.createError("VLQ-encoded source index expected") + sourceLine += stream.readInt() ?: return stream.createError("VLQ-encoded source line expected") + sourceColumn += stream.readInt() ?: return stream.createError("VLQ-encoded source column expected") + if (stream.isEncodedInt) { + stream.readInt() ?: return stream.createError("VLQ-encoded name index expected") + } + } + + if (sourceIndex !in sources.indices) { + return stream.createError("Source index $sourceIndex is out of bounds ${sources.indices}") + } + + currentGroup.segments += SourceMapSegment(jsColumn, sourceRoot + sources[sourceIndex], sourceLine, sourceColumn) + + when { + stream.isEof -> return stream.createError("Unexpected EOF, ',' or ';' expected") + stream.isGroupTerminator -> { + currentGroup = SourceMapGroup().also { sourceMap.groups += it } + jsColumn = 0 + } + !stream.isSegmentTerminator -> return stream.createError("Unexpected char, ',' or ';' expected") + } + stream.skipChar() + } + + return SourceMapSuccess(sourceMap) + } + + internal class MappingStream(val string: String) { + var position = 0 + + val isEof: Boolean get() = position == string.length + + val isSegmentTerminator: Boolean get() = string[position] == ',' + + val isGroupTerminator: Boolean get() = string[position] == ';' + + val isEncodedInt: Boolean get() = !isEof && !isSegmentTerminator && !isGroupTerminator + + fun skipChar() { + position++ + } + + fun readInt(): Int? { + var value = 0 + var shift = 0 + while (true) { + if (isEof) return null + val digit = base64value(string[position++]) ?: return null + + val digitValue = digit and 0x1F + value = value or (digitValue shl shift) + + if ((digit and 0x20) == 0) break + shift += 5 + } + + val unsignedValue = value ushr 1 + return if ((value and 1) == 0) unsignedValue else -unsignedValue + } + + private fun base64value(c: Char): Int? = when (c) { + in 'A'..'Z' -> c.toInt() - 'A'.toInt() + in 'a'..'z' -> c.toInt() - 'a'.toInt() + 26 + in '0'..'9' -> c.toInt() - '0'.toInt() + 52 + '+' -> 62 + '/' -> 63 + else -> null + } + + fun createError(error: String): SourceMapError = SourceMapError("Error parsing stream at offset $position: $error") + } +} diff --git a/update_dependencies.xml b/update_dependencies.xml index 9ed51affeea..e7dfbbba92e 100644 --- a/update_dependencies.xml +++ b/update_dependencies.xml @@ -295,6 +295,9 @@ + + +