Refactor generator of JS source map
- refactor pipeline for generation of source map - generate "empty" mappings for nodes that impossible to map to something reasonable - generate more accurate locations in source maps for specific JS AST nodes - for binary operation nodes parser now returns location of binary operator tokens instead of location of first operand - change completely how source map remapper works
This commit is contained in:
@@ -1115,7 +1115,10 @@ public class JsAstMapper {
|
||||
astNode.setSource(jsLocation);
|
||||
}
|
||||
else if (astNode instanceof JsExpressionStatement) {
|
||||
((JsExpressionStatement) astNode).getExpression().setSource(jsLocation);
|
||||
JsExpression expression = ((JsExpressionStatement) astNode).getExpression();
|
||||
if (expression.getSource() == null) {
|
||||
expression.setSource(jsLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
return astNode;
|
||||
|
||||
@@ -180,7 +180,7 @@ public class Parser extends Observable {
|
||||
// by '(', assume <name> starts memberExpr
|
||||
Node memberExprHead = nameNode;
|
||||
nameNode = null;
|
||||
memberExprNode = memberExprTail(ts, false, memberExprHead, basePosition);
|
||||
memberExprNode = memberExprTail(ts, false, memberExprHead);
|
||||
}
|
||||
mustMatchToken(ts, TokenStream.LP, "msg.no.paren.parms");
|
||||
}
|
||||
@@ -708,20 +708,20 @@ public class Parser extends Observable {
|
||||
}
|
||||
|
||||
private Node expr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = assignExpr(ts, inForInit);
|
||||
while (ts.matchToken(TokenStream.COMMA)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.COMMA, pn, assignExpr(ts, inForInit), position);
|
||||
}
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Node assignExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = condExpr(ts, inForInit);
|
||||
|
||||
if (ts.matchToken(TokenStream.ASSIGN)) {
|
||||
// omitted: "invalid assignment left-hand side" check.
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit), position);
|
||||
}
|
||||
|
||||
@@ -729,10 +729,10 @@ public class Parser extends Observable {
|
||||
}
|
||||
|
||||
private Node condExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = orExpr(ts, inForInit);
|
||||
|
||||
if (ts.matchToken(TokenStream.HOOK)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
Node ifTrue = assignExpr(ts, false);
|
||||
mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.cond");
|
||||
Node ifFalse = assignExpr(ts, inForInit);
|
||||
@@ -743,9 +743,9 @@ public class Parser extends Observable {
|
||||
}
|
||||
|
||||
private Node orExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = andExpr(ts, inForInit);
|
||||
while (ts.matchToken(TokenStream.OR)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.OR, pn, andExpr(ts, inForInit), position);
|
||||
}
|
||||
|
||||
@@ -753,9 +753,9 @@ public class Parser extends Observable {
|
||||
}
|
||||
|
||||
private Node andExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = bitOrExpr(ts, inForInit);
|
||||
while (ts.matchToken(TokenStream.AND)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.AND, pn, bitOrExpr(ts, inForInit), position);
|
||||
}
|
||||
|
||||
@@ -763,44 +763,46 @@ public class Parser extends Observable {
|
||||
}
|
||||
|
||||
private Node bitOrExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = bitXorExpr(ts, inForInit);
|
||||
while (ts.matchToken(TokenStream.BITOR)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.BITOR, pn, bitXorExpr(ts, inForInit), position);
|
||||
}
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Node bitXorExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = bitAndExpr(ts, inForInit);
|
||||
while (ts.matchToken(TokenStream.BITXOR)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.BITXOR, pn, bitAndExpr(ts, inForInit), position);
|
||||
}
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Node bitAndExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = eqExpr(ts, inForInit);
|
||||
while (ts.matchToken(TokenStream.BITAND)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.BITAND, pn, eqExpr(ts, inForInit), position);
|
||||
}
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Node eqExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = relExpr(ts, inForInit);
|
||||
|
||||
while (ts.matchToken(TokenStream.EQOP)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.EQOP, ts.getOp(), pn, relExpr(ts, inForInit), position);
|
||||
}
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Node relExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = shiftExpr(ts);
|
||||
CodePosition position = ts.tokenPosition;
|
||||
|
||||
while (ts.matchToken(TokenStream.RELOP)) {
|
||||
int op = ts.getOp();
|
||||
if (inForInit && op == TokenStream.IN) {
|
||||
@@ -809,26 +811,26 @@ public class Parser extends Observable {
|
||||
}
|
||||
|
||||
pn = nf.createBinary(TokenStream.RELOP, op, pn, shiftExpr(ts), position);
|
||||
position = ts.tokenPosition;
|
||||
}
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Node shiftExpr(TokenStream ts) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
Node pn = addExpr(ts);
|
||||
while (ts.matchToken(TokenStream.SHOP)) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(TokenStream.SHOP, ts.getOp(), pn, addExpr(ts), position);
|
||||
}
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Node addExpr(TokenStream ts) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
int tt;
|
||||
Node pn = mulExpr(ts);
|
||||
|
||||
while ((tt = ts.getToken()) == TokenStream.ADD || tt == TokenStream.SUB) {
|
||||
// flushNewLines
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(tt, pn, mulExpr(ts), position);
|
||||
}
|
||||
ts.ungetToken(tt);
|
||||
@@ -837,13 +839,13 @@ public class Parser extends Observable {
|
||||
}
|
||||
|
||||
private Node mulExpr(TokenStream ts) throws IOException, JavaScriptException {
|
||||
CodePosition position = getNextTokenPosition(ts);
|
||||
int tt;
|
||||
|
||||
Node pn = unaryExpr(ts);
|
||||
|
||||
while ((tt = ts.peekToken()) == TokenStream.MUL || tt == TokenStream.DIV || tt == TokenStream.MOD) {
|
||||
tt = ts.getToken();
|
||||
CodePosition position = ts.tokenPosition;
|
||||
pn = nf.createBinary(tt, pn, unaryExpr(ts), position);
|
||||
}
|
||||
|
||||
@@ -894,6 +896,7 @@ public class Parser extends Observable {
|
||||
if (((peeked = ts.peekToken()) == TokenStream.INC || peeked == TokenStream.DEC)
|
||||
&& ts.getLineno() == lineno) {
|
||||
int pf = ts.getToken();
|
||||
position = ts.tokenPosition;
|
||||
return nf.createUnary(pf, TokenStream.POST, pn, position);
|
||||
}
|
||||
return pn;
|
||||
@@ -960,16 +963,17 @@ public class Parser extends Observable {
|
||||
pn = primaryExpr(ts);
|
||||
}
|
||||
|
||||
return memberExprTail(ts, allowCallSyntax, pn, position);
|
||||
return memberExprTail(ts, allowCallSyntax, pn);
|
||||
}
|
||||
|
||||
private Node memberExprTail(
|
||||
TokenStream ts, boolean allowCallSyntax,
|
||||
Node pn, CodePosition position
|
||||
Node pn
|
||||
) throws IOException, JavaScriptException {
|
||||
lastExprEndLine = ts.getLineno();
|
||||
int tt;
|
||||
while ((tt = ts.getToken()) > TokenStream.EOF) {
|
||||
CodePosition position = ts.tokenPosition;
|
||||
if (tt == TokenStream.DOT) {
|
||||
ts.treatKeywordAsIdentifier = true;
|
||||
mustMatchToken(ts, TokenStream.NAME, "msg.no.name.after.dot");
|
||||
@@ -1144,13 +1148,6 @@ public class Parser extends Observable {
|
||||
return null; // should never reach here
|
||||
}
|
||||
|
||||
private static CodePosition getNextTokenPosition(TokenStream ts) throws IOException, JavaScriptException {
|
||||
int tt = ts.getToken();
|
||||
CodePosition result = ts.tokenPosition;
|
||||
ts.ungetToken(tt);
|
||||
return result;
|
||||
}
|
||||
|
||||
private int lastExprEndLine; // Hack to handle function expr termination.
|
||||
private final IRFactory nf;
|
||||
private boolean ok; // Did the parse encounter an error?
|
||||
|
||||
@@ -16,15 +16,26 @@
|
||||
|
||||
package org.jetbrains.kotlin.js.parser.sourcemaps
|
||||
|
||||
import java.io.PrintStream
|
||||
import java.io.Reader
|
||||
|
||||
class SourceMap(val sourceContentResolver: (String) -> Reader?) {
|
||||
val groups = mutableListOf<SourceMapGroup>()
|
||||
|
||||
fun debug(writer: PrintStream = System.out) {
|
||||
for ((index, group) in groups.withIndex()) {
|
||||
writer.print("${index + 1}:")
|
||||
for (segment in group.segments) {
|
||||
writer.print(" ${segment.generatedColumnNumber + 1}:${segment.sourceLineNumber + 1},${segment.sourceColumnNumber + 1}")
|
||||
}
|
||||
writer.println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SourceMapSegment(
|
||||
val generatedColumnNumber: Int,
|
||||
val sourceFileName: String,
|
||||
val sourceFileName: String?,
|
||||
val sourceLineNumber: Int,
|
||||
val sourceColumnNumber: Int
|
||||
)
|
||||
|
||||
+98
-31
@@ -18,51 +18,118 @@ package org.jetbrains.kotlin.js.parser.sourcemaps
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
|
||||
class SourceMapLocationRemapper(val sourceMaps: Map<String, SourceMap>) {
|
||||
class SourceMapLocationRemapper(private val sourceMap: SourceMap) {
|
||||
fun remap(node: JsNode) {
|
||||
node.accept(visitor)
|
||||
val listCollector = JsNodeFlatListCollector()
|
||||
node.accept(listCollector)
|
||||
applySourceMap(listCollector.nodeList)
|
||||
}
|
||||
|
||||
private val visitor = object : RecursiveJsVisitor() {
|
||||
private var lastSourceMap: SourceMap? = null
|
||||
private var lastGroup: SourceMapGroup? = null
|
||||
private var lastSegmentIndex = 0
|
||||
private fun applySourceMap(nodes: List<JsNode>) {
|
||||
var lastGroup: SourceMapGroup? = null
|
||||
var lastGroupIndex = 0
|
||||
var lastSegment: SourceMapSegment? = null
|
||||
var lastSegmentIndex = 0
|
||||
|
||||
override fun visitElement(node: JsNode) {
|
||||
if (node is SourceInfoAwareJsNode) {
|
||||
if (!remapNode(node)) {
|
||||
node.source = null
|
||||
fun findCorrespondingSegment(node: SourceInfoAwareJsNode): SourceMapSegment? {
|
||||
val source = node.source as? JsLocation ?: return null
|
||||
val group = sourceMap.groups.getOrElse(source.startLine) { return null }
|
||||
|
||||
if (lastGroup != group) {
|
||||
if (lastGroup != null) {
|
||||
val segmentsToSkip = lastGroup!!.segments.drop(lastSegmentIndex).toMutableList()
|
||||
if (lastGroupIndex + 1 < source.startLine) {
|
||||
segmentsToSkip += sourceMap.groups.subList((lastGroupIndex + 1), source.startLine).flatMap { it.segments }
|
||||
}
|
||||
|
||||
segmentsToSkip.lastOrNull()?.let { lastSegment = it }
|
||||
}
|
||||
lastGroup = group
|
||||
lastGroupIndex = source.startLine
|
||||
lastSegmentIndex = 0
|
||||
}
|
||||
super.visitElement(node)
|
||||
|
||||
while (lastSegmentIndex < group.segments.size) {
|
||||
val segment = group.segments[lastSegmentIndex]
|
||||
if (segment.generatedColumnNumber > source.startChar) break
|
||||
|
||||
lastSegment = segment
|
||||
lastSegmentIndex++
|
||||
}
|
||||
|
||||
return lastSegment
|
||||
}
|
||||
|
||||
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
|
||||
for (node in nodes.asSequence().filterIsInstance<SourceInfoAwareJsNode>()) {
|
||||
val segment = findCorrespondingSegment(node)
|
||||
val sourceFileName = segment?.sourceFileName
|
||||
node.source = if (sourceFileName != null) {
|
||||
val location = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber)
|
||||
JsLocationWithEmbeddedSource(location, sourceMap) { sourceMap.sourceContentResolver(segment.sourceFileName) }
|
||||
}
|
||||
if (group.segments[lastSegmentIndex].generatedColumnNumber > source.startChar) {
|
||||
if (lastSegmentIndex == 0) return false
|
||||
lastSegmentIndex = 0
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (lastSegmentIndex + 1 < group.segments.size) {
|
||||
val nextIndex = lastSegmentIndex + 1
|
||||
if (group.segments[nextIndex].generatedColumnNumber > source.startChar) break
|
||||
lastSegmentIndex = nextIndex
|
||||
internal class JsNodeFlatListCollector : RecursiveJsVisitor() {
|
||||
val nodeList = mutableListOf<JsNode>()
|
||||
|
||||
override fun visitDoWhile(x: JsDoWhile) {
|
||||
nodeList += x
|
||||
accept(x.body)
|
||||
accept(x.condition)
|
||||
}
|
||||
|
||||
override fun visitBinaryExpression(x: JsBinaryOperation) = handleNode(x, x.arg1, x.arg2)
|
||||
|
||||
override fun visitConditional(x: JsConditional) = handleNode(x, x.testExpression, x.thenExpression, x.elseExpression)
|
||||
|
||||
override fun visitArrayAccess(x: JsArrayAccess) = handleNode(x, x.arrayExpression, x.indexExpression)
|
||||
|
||||
override fun visitArray(x: JsArrayLiteral) = handleNode(x, *x.expressions.toTypedArray())
|
||||
|
||||
override fun visitPrefixOperation(x: JsPrefixOperation) = handleNode(x, x.arg)
|
||||
|
||||
override fun visitPostfixOperation(x: JsPostfixOperation) = handleNode(x, x.arg)
|
||||
|
||||
override fun visitNameRef(nameRef: JsNameRef) = handleNode(nameRef, nameRef.qualifier)
|
||||
|
||||
override fun visitInvocation(invocation: JsInvocation) =
|
||||
handleNode(invocation, invocation.qualifier, *invocation.arguments.toTypedArray())
|
||||
|
||||
override fun visitElement(node: JsNode) {
|
||||
nodeList += node
|
||||
node.acceptChildren(this)
|
||||
}
|
||||
|
||||
private fun handleNode(node: JsNode, vararg children: JsNode?) {
|
||||
val nonNullChildren = children.mapNotNull { it }
|
||||
|
||||
if (nonNullChildren.isEmpty()) {
|
||||
nodeList += node
|
||||
}
|
||||
else {
|
||||
val firstChild = nonNullChildren.first()
|
||||
if (node.isNotBefore(firstChild)) {
|
||||
accept(firstChild)
|
||||
nodeList += node
|
||||
nonNullChildren.drop(1).forEach { accept(it) }
|
||||
}
|
||||
else {
|
||||
nodeList += node
|
||||
nonNullChildren.forEach { accept(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val segment = group.segments[lastSegmentIndex]
|
||||
val location = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber)
|
||||
node.source = JsLocationWithEmbeddedSource(location, sourceMap) { sourceMap.sourceContentResolver(segment.sourceFileName) }
|
||||
|
||||
return true
|
||||
private fun JsNode.isNotBefore(other: JsNode): Boolean {
|
||||
val first = (source as? JsLocation ?: return false)
|
||||
val second = (other.source as? JsLocation ?: return false)
|
||||
if (first.file != second.file) return false
|
||||
return first.startLine > second.startLine || (first.startLine == second.startLine && first.startChar >= second.startChar)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,22 +14,6 @@
|
||||
* 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
|
||||
@@ -115,13 +99,15 @@ object SourceMapParser {
|
||||
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}")
|
||||
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)
|
||||
}
|
||||
else {
|
||||
currentGroup.segments += SourceMapSegment(jsColumn, null, -1, -1)
|
||||
}
|
||||
|
||||
currentGroup.segments += SourceMapSegment(jsColumn, sourceRoot + sources[sourceIndex], sourceLine, sourceColumn)
|
||||
|
||||
when {
|
||||
stream.isEof -> return stream.createError("Unexpected EOF, ',' or ';' expected")
|
||||
|
||||
Reference in New Issue
Block a user