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:
Alexey Andreev
2017-06-20 15:34:36 +03:00
parent 6a2f3d8065
commit dea73ebecf
30 changed files with 622 additions and 336 deletions
@@ -12,10 +12,7 @@ import org.jetbrains.kotlin.js.util.TextOutput;
import gnu.trove.THashSet; import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* Produces text output from a JavaScript AST. * Produces text output from a JavaScript AST.
@@ -47,6 +44,11 @@ public class JsToStringGenerationVisitor extends JsVisitor {
private static final char[] CHARS_WHILE = "while".toCharArray(); private static final char[] CHARS_WHILE = "while".toCharArray();
private static final char[] HEX_DIGITS = { private static final char[] HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
@NotNull
private final SourceLocationConsumer sourceLocationConsumer;
@NotNull
private final List<Object> sourceInfoStack = new ArrayList<>();
public static CharSequence javaScriptString(String value) { public static CharSequence javaScriptString(String value) {
return javaScriptString(value, false); return javaScriptString(value, false);
@@ -60,7 +62,7 @@ public class JsToStringGenerationVisitor extends JsVisitor {
*/ */
@SuppressWarnings({"ConstantConditions", "UnnecessaryFullyQualifiedName", "JavadocReference"}) @SuppressWarnings({"ConstantConditions", "UnnecessaryFullyQualifiedName", "JavadocReference"})
public static CharSequence javaScriptString(CharSequence chars, boolean forceDoubleQuote) { public static CharSequence javaScriptString(CharSequence chars, boolean forceDoubleQuote) {
final int n = chars.length(); int n = chars.length();
int quoteCount = 0; int quoteCount = 0;
int aposCount = 0; int aposCount = 0;
@@ -174,25 +176,40 @@ public class JsToStringGenerationVisitor extends JsVisitor {
* those that appear directly within these global blocks. * those that appear directly within these global blocks.
*/ */
private Set<JsBlock> globalBlocks = new THashSet<JsBlock>(); private Set<JsBlock> globalBlocks = new THashSet<JsBlock>();
@NotNull
protected final TextOutput p; protected final TextOutput p;
public JsToStringGenerationVisitor(TextOutput out) { public JsToStringGenerationVisitor(@NotNull TextOutput out, @NotNull SourceLocationConsumer sourceLocationConsumer) {
p = out; p = out;
this.sourceLocationConsumer = sourceLocationConsumer;
}
public JsToStringGenerationVisitor(@NotNull TextOutput out) {
this(out, NoOpSourceLocationConsumer.INSTANCE);
} }
@Override @Override
public void visitArrayAccess(@NotNull JsArrayAccess x) { public void visitArrayAccess(@NotNull JsArrayAccess x) {
pushSourceInfo(x.getSource());
printPair(x, x.getArrayExpression()); printPair(x, x.getArrayExpression());
leftSquare(); leftSquare();
accept(x.getIndexExpression()); accept(x.getIndexExpression());
rightSquare(); rightSquare();
popSourceInfo();
} }
@Override @Override
public void visitArray(@NotNull JsArrayLiteral x) { public void visitArray(@NotNull JsArrayLiteral x) {
pushSourceInfo(x.getSource());
leftSquare(); leftSquare();
printExpressions(x.getExpressions()); printExpressions(x.getExpressions());
rightSquare(); rightSquare();
popSourceInfo();
} }
private void printExpressions(List<JsExpression> expressions) { private void printExpressions(List<JsExpression> expressions) {
@@ -209,6 +226,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitBinaryExpression(@NotNull JsBinaryOperation binaryOperation) { public void visitBinaryExpression(@NotNull JsBinaryOperation binaryOperation) {
pushSourceInfo(binaryOperation.getSource());
JsBinaryOperator operator = binaryOperation.getOperator(); JsBinaryOperator operator = binaryOperation.getOperator();
JsExpression arg1 = binaryOperation.getArg1(); JsExpression arg1 = binaryOperation.getArg1();
boolean isExpressionEnclosed = parenPush(binaryOperation, arg1, !operator.isLeftAssociative()); boolean isExpressionEnclosed = parenPush(binaryOperation, arg1, !operator.isLeftAssociative());
@@ -250,6 +269,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
if (isParenOpened) { if (isParenOpened) {
rightParen(); rightParen();
} }
popSourceInfo();
} }
@Override @Override
@@ -259,29 +280,41 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitBoolean(@NotNull JsBooleanLiteral x) { public void visitBoolean(@NotNull JsBooleanLiteral x) {
pushSourceInfo(x.getSource());
if (x.getValue()) { if (x.getValue()) {
p.print(CHARS_TRUE); p.print(CHARS_TRUE);
} }
else { else {
p.print(CHARS_FALSE); p.print(CHARS_FALSE);
} }
popSourceInfo();
} }
@Override @Override
public void visitBreak(@NotNull JsBreak x) { public void visitBreak(@NotNull JsBreak x) {
pushSourceInfo(x.getSource());
p.print(CHARS_BREAK); p.print(CHARS_BREAK);
continueOrBreakLabel(x); continueOrBreakLabel(x);
popSourceInfo();
} }
@Override @Override
public void visitContinue(@NotNull JsContinue x) { public void visitContinue(@NotNull JsContinue x) {
pushSourceInfo(x.getSource());
p.print(CHARS_CONTINUE); p.print(CHARS_CONTINUE);
continueOrBreakLabel(x); continueOrBreakLabel(x);
popSourceInfo();
} }
private void continueOrBreakLabel(JsContinue x) { private void continueOrBreakLabel(JsContinue x) {
JsNameRef label = x.getLabel(); JsNameRef label = x.getLabel();
if (label != null && label.getIdent() != null) { if (label != null) {
space(); space();
p.print(label.getIdent()); p.print(label.getIdent());
} }
@@ -289,13 +322,20 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitCase(@NotNull JsCase x) { public void visitCase(@NotNull JsCase x) {
pushSourceInfo(x.getSource());
p.print(CHARS_CASE); p.print(CHARS_CASE);
space(); space();
accept(x.getCaseExpression()); accept(x.getCaseExpression());
_colon(); _colon();
popSourceInfo();
newlineOpt(); newlineOpt();
sourceLocationConsumer.pushSourceInfo(null);
printSwitchMemberStatements(x); printSwitchMemberStatements(x);
sourceLocationConsumer.popSourceInfo();
} }
private void printSwitchMemberStatements(JsSwitchMember x) { private void printSwitchMemberStatements(JsSwitchMember x) {
@@ -314,6 +354,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitCatch(@NotNull JsCatch x) { public void visitCatch(@NotNull JsCatch x) {
pushSourceInfo(x.getSource());
spaceOpt(); spaceOpt();
p.print(CHARS_CATCH); p.print(CHARS_CATCH);
spaceOpt(); spaceOpt();
@@ -332,11 +374,18 @@ public class JsToStringGenerationVisitor extends JsVisitor {
rightParen(); rightParen();
spaceOpt(); spaceOpt();
popSourceInfo();
sourceLocationConsumer.pushSourceInfo(null);
accept(x.getBody()); accept(x.getBody());
sourceLocationConsumer.popSourceInfo();
} }
@Override @Override
public void visitConditional(@NotNull JsConditional x) { public void visitConditional(@NotNull JsConditional x) {
pushSourceInfo(x.getSource());
// Associativity: for the then and else branches, it is safe to insert // Associativity: for the then and else branches, it is safe to insert
// another // another
// ternary expression, but if the test expression is a ternary, it should // ternary expression, but if the test expression is a ternary, it should
@@ -350,6 +399,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
_colon(); _colon();
spaceOpt(); spaceOpt();
printPair(x, x.getElseExpression()); printPair(x, x.getElseExpression());
popSourceInfo();
} }
private void printPair(JsExpression parent, JsExpression expression, boolean wrongAssoc) { private void printPair(JsExpression parent, JsExpression expression, boolean wrongAssoc) {
@@ -369,35 +420,57 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitDebugger(@NotNull JsDebugger x) { public void visitDebugger(@NotNull JsDebugger x) {
pushSourceInfo(x.getSource());
p.print(CHARS_DEBUGGER); p.print(CHARS_DEBUGGER);
popSourceInfo();
} }
@Override @Override
public void visitDefault(@NotNull JsDefault x) { public void visitDefault(@NotNull JsDefault x) {
pushSourceInfo(x.getSource());
p.print(CHARS_DEFAULT); p.print(CHARS_DEFAULT);
_colon(); _colon();
popSourceInfo();
sourceLocationConsumer.pushSourceInfo(null);
printSwitchMemberStatements(x); printSwitchMemberStatements(x);
sourceLocationConsumer.popSourceInfo();
} }
@Override @Override
public void visitWhile(@NotNull JsWhile x) { public void visitWhile(@NotNull JsWhile x) {
pushSourceInfo(x.getSource());
_while(); _while();
spaceOpt(); spaceOpt();
leftParen(); leftParen();
accept(x.getCondition()); accept(x.getCondition());
rightParen(); rightParen();
popSourceInfo();
nestedPush(x.getBody()); nestedPush(x.getBody());
sourceLocationConsumer.pushSourceInfo(null);
accept(x.getBody()); accept(x.getBody());
sourceLocationConsumer.popSourceInfo();
nestedPop(x.getBody()); nestedPop(x.getBody());
} }
@Override @Override
public void visitDoWhile(@NotNull JsDoWhile x) { public void visitDoWhile(@NotNull JsDoWhile x) {
sourceLocationConsumer.pushSourceInfo(null);
p.print(CHARS_DO); p.print(CHARS_DO);
nestedPush(x.getBody()); nestedPush(x.getBody());
accept(x.getBody()); accept(x.getBody());
sourceLocationConsumer.popSourceInfo();
nestedPop(x.getBody()); nestedPop(x.getBody());
pushSourceInfo(x.getCondition().getSource());
if (needSemi) { if (needSemi) {
semi(); semi();
newlineOpt(); newlineOpt();
@@ -406,11 +479,14 @@ public class JsToStringGenerationVisitor extends JsVisitor {
spaceOpt(); spaceOpt();
needSemi = true; needSemi = true;
} }
_while(); _while();
spaceOpt(); spaceOpt();
leftParen(); leftParen();
accept(x.getCondition()); accept(x.getCondition());
rightParen(); rightParen();
popSourceInfo();
} }
@Override @Override
@@ -419,6 +495,12 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitExpressionStatement(@NotNull JsExpressionStatement x) { public void visitExpressionStatement(@NotNull JsExpressionStatement x) {
Object source = x.getSource();
if (source == null) {
source = x.getExpression().getSource();
}
pushSourceInfo(source);
boolean surroundWithParentheses = JsFirstExpressionVisitor.exec(x); boolean surroundWithParentheses = JsFirstExpressionVisitor.exec(x);
if (surroundWithParentheses) { if (surroundWithParentheses) {
leftParen(); leftParen();
@@ -427,10 +509,14 @@ public class JsToStringGenerationVisitor extends JsVisitor {
if (surroundWithParentheses) { if (surroundWithParentheses) {
rightParen(); rightParen();
} }
popSourceInfo();
} }
@Override @Override
public void visitFor(@NotNull JsFor x) { public void visitFor(@NotNull JsFor x) {
pushSourceInfo(x.getSource());
_for(); _for();
spaceOpt(); spaceOpt();
leftParen(); leftParen();
@@ -463,15 +549,22 @@ public class JsToStringGenerationVisitor extends JsVisitor {
} }
rightParen(); rightParen();
popSourceInfo();
nestedPush(x.getBody()); nestedPush(x.getBody());
if (x.getBody() != null) { if (x.getBody() != null) {
sourceLocationConsumer.pushSourceInfo(null);
accept(x.getBody()); accept(x.getBody());
sourceLocationConsumer.popSourceInfo();
} }
nestedPop(x.getBody()); nestedPop(x.getBody());
} }
@Override @Override
public void visitForIn(@NotNull JsForIn x) { public void visitForIn(@NotNull JsForIn x) {
pushSourceInfo(x.getSource());
_for(); _for();
spaceOpt(); spaceOpt();
leftParen(); leftParen();
@@ -500,13 +593,20 @@ public class JsToStringGenerationVisitor extends JsVisitor {
accept(x.getObjectExpression()); accept(x.getObjectExpression());
rightParen(); rightParen();
popSourceInfo();
nestedPush(x.getBody()); nestedPush(x.getBody());
sourceLocationConsumer.pushSourceInfo(null);
accept(x.getBody()); accept(x.getBody());
sourceLocationConsumer.popSourceInfo();
nestedPop(x.getBody()); nestedPop(x.getBody());
} }
@Override @Override
public void visitFunction(@NotNull JsFunction x) { public void visitFunction(@NotNull JsFunction x) {
pushSourceInfo(x.getSource());
p.print(CHARS_FUNCTION); p.print(CHARS_FUNCTION);
space(); space();
if (x.getName() != null) { if (x.getName() != null) {
@@ -524,24 +624,39 @@ public class JsToStringGenerationVisitor extends JsVisitor {
space(); space();
lineBreakAfterBlock = false; lineBreakAfterBlock = false;
sourceLocationConsumer.pushSourceInfo(null);
accept(x.getBody()); accept(x.getBody());
sourceLocationConsumer.popSourceInfo();
needSemi = true; needSemi = true;
popSourceInfo();
} }
@Override @Override
public void visitIf(@NotNull JsIf x) { public void visitIf(@NotNull JsIf x) {
pushSourceInfo(x.getSource());
_if(); _if();
spaceOpt(); spaceOpt();
leftParen(); leftParen();
accept(x.getIfExpression()); accept(x.getIfExpression());
rightParen(); rightParen();
popSourceInfo();
JsStatement thenStmt = x.getThenStatement(); JsStatement thenStmt = x.getThenStatement();
JsStatement elseStatement = x.getElseStatement(); JsStatement elseStatement = x.getElseStatement();
if (elseStatement != null && thenStmt instanceof JsIf && ((JsIf)thenStmt).getElseStatement() == null) { if (elseStatement != null && thenStmt instanceof JsIf && ((JsIf)thenStmt).getElseStatement() == null) {
thenStmt = new JsBlock(thenStmt); thenStmt = new JsBlock(thenStmt);
} }
nestedPush(thenStmt); nestedPush(thenStmt);
sourceLocationConsumer.pushSourceInfo(null);
accept(thenStmt); accept(thenStmt);
sourceLocationConsumer.popSourceInfo();
nestedPop(thenStmt); nestedPop(thenStmt);
if (elseStatement != null) { if (elseStatement != null) {
if (needSemi) { if (needSemi) {
@@ -560,7 +675,9 @@ public class JsToStringGenerationVisitor extends JsVisitor {
else { else {
space(); space();
} }
sourceLocationConsumer.pushSourceInfo(null);
accept(elseStatement); accept(elseStatement);
sourceLocationConsumer.popSourceInfo();
if (!elseIf) { if (!elseIf) {
nestedPop(elseStatement); nestedPop(elseStatement);
} }
@@ -569,11 +686,15 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitInvocation(@NotNull JsInvocation invocation) { public void visitInvocation(@NotNull JsInvocation invocation) {
pushSourceInfo(invocation.getSource());
printPair(invocation, invocation.getQualifier()); printPair(invocation, invocation.getQualifier());
leftParen(); leftParen();
printExpressions(invocation.getArguments()); printExpressions(invocation.getArguments());
rightParen(); rightParen();
popSourceInfo();
} }
@Override @Override
@@ -581,14 +702,19 @@ public class JsToStringGenerationVisitor extends JsVisitor {
nameOf(x); nameOf(x);
_colon(); _colon();
spaceOpt(); spaceOpt();
sourceLocationConsumer.pushSourceInfo(null);
accept(x.getStatement()); accept(x.getStatement());
sourceLocationConsumer.popSourceInfo();
} }
@Override @Override
public void visitNameRef(@NotNull JsNameRef nameRef) { public void visitNameRef(@NotNull JsNameRef nameRef) {
pushSourceInfo(nameRef.getSource());
JsExpression qualifier = nameRef.getQualifier(); JsExpression qualifier = nameRef.getQualifier();
if (qualifier != null) { if (qualifier != null) {
final boolean enclose; boolean enclose;
if (qualifier instanceof JsLiteral.JsValueLiteral) { if (qualifier instanceof JsLiteral.JsValueLiteral) {
// "42.foo" is not allowed, but "(42).foo" is. // "42.foo" is not allowed, but "(42).foo" is.
enclose = qualifier instanceof JsNumberLiteral; enclose = qualifier instanceof JsNumberLiteral;
@@ -609,10 +735,14 @@ public class JsToStringGenerationVisitor extends JsVisitor {
p.maybeIndent(); p.maybeIndent();
p.print(nameRef.getIdent()); p.print(nameRef.getIdent());
popSourceInfo();
} }
@Override @Override
public void visitNew(@NotNull JsNew x) { public void visitNew(@NotNull JsNew x) {
pushSourceInfo(x.getSource());
p.print(CHARS_NEW); p.print(CHARS_NEW);
space(); space();
@@ -629,26 +759,43 @@ public class JsToStringGenerationVisitor extends JsVisitor {
leftParen(); leftParen();
printExpressions(x.getArguments()); printExpressions(x.getArguments());
rightParen(); rightParen();
popSourceInfo();
} }
@Override @Override
public void visitNull(@NotNull JsNullLiteral x) { public void visitNull(@NotNull JsNullLiteral x) {
pushSourceInfo(x.getSource());
p.print(CHARS_NULL); p.print(CHARS_NULL);
popSourceInfo();
} }
@Override @Override
public void visitInt(@NotNull JsIntLiteral x) { public void visitInt(@NotNull JsIntLiteral x) {
pushSourceInfo(x.getSource());
p.print(x.value); p.print(x.value);
popSourceInfo();
} }
@Override @Override
public void visitDouble(@NotNull JsDoubleLiteral x) { public void visitDouble(@NotNull JsDoubleLiteral x) {
pushSourceInfo(x.getSource());
p.print(x.value); p.print(x.value);
popSourceInfo();
} }
@Override @Override
public void visitObjectLiteral(@NotNull JsObjectLiteral objectLiteral) { public void visitObjectLiteral(@NotNull JsObjectLiteral objectLiteral) {
pushSourceInfo(objectLiteral.getSource());
p.print('{'); p.print('{');
if (objectLiteral.isMultiline()) { if (objectLiteral.isMultiline()) {
p.indentIn(); p.indentIn();
} }
@@ -668,6 +815,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
notFirst = true; notFirst = true;
pushSourceInfo(item.getSource());
JsExpression labelExpr = item.getLabelExpr(); JsExpression labelExpr = item.getLabelExpr();
// labels can be either string, integral, or decimal literals // labels can be either string, integral, or decimal literals
if (labelExpr instanceof JsNameRef) { if (labelExpr instanceof JsNameRef) {
@@ -688,6 +837,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
if (wasEnclosed) { if (wasEnclosed) {
rightParen(); rightParen();
} }
popSourceInfo();
} }
if (objectLiteral.isMultiline()) { if (objectLiteral.isMultiline()) {
@@ -696,6 +847,7 @@ public class JsToStringGenerationVisitor extends JsVisitor {
} }
p.print('}'); p.print('}');
popSourceInfo();
} }
@Override @Override
@@ -705,15 +857,21 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visitPostfixOperation(@NotNull JsPostfixOperation x) { public void visitPostfixOperation(@NotNull JsPostfixOperation x) {
pushSourceInfo(x.getSource());
JsUnaryOperator op = x.getOperator(); JsUnaryOperator op = x.getOperator();
JsExpression arg = x.getArg(); JsExpression arg = x.getArg();
// unary operators always associate correctly (I think) // unary operators always associate correctly (I think)
printPair(x, arg); printPair(x, arg);
p.print(op.getSymbol()); p.print(op.getSymbol());
popSourceInfo();
} }
@Override @Override
public void visitPrefixOperation(@NotNull JsPrefixOperation x) { public void visitPrefixOperation(@NotNull JsPrefixOperation x) {
pushSourceInfo(x.getSource());
JsUnaryOperator op = x.getOperator(); JsUnaryOperator op = x.getOperator();
p.print(op.getSymbol()); p.print(op.getSymbol());
JsExpression arg = x.getArg(); JsExpression arg = x.getArg();
@@ -722,15 +880,19 @@ public class JsToStringGenerationVisitor extends JsVisitor {
} }
// unary operators always associate correctly (I think) // unary operators always associate correctly (I think)
printPair(x, arg); printPair(x, arg);
popSourceInfo();
} }
@Override @Override
public void visitProgram(@NotNull JsProgram x) { public void visitProgram(@NotNull JsProgram x) {
p.print("<JsProgram>"); x.acceptChildren(this);
} }
@Override @Override
public void visitRegExp(@NotNull JsRegExp x) { public void visitRegExp(@NotNull JsRegExp x) {
pushSourceInfo(x.getSource());
slash(); slash();
p.print(x.getPattern()); p.print(x.getPattern());
slash(); slash();
@@ -738,46 +900,72 @@ public class JsToStringGenerationVisitor extends JsVisitor {
if (flags != null) { if (flags != null) {
p.print(flags); p.print(flags);
} }
popSourceInfo();
} }
@Override @Override
public void visitReturn(@NotNull JsReturn x) { public void visitReturn(@NotNull JsReturn x) {
pushSourceInfo(x.getSource());
p.print(CHARS_RETURN); p.print(CHARS_RETURN);
JsExpression expr = x.getExpression(); JsExpression expr = x.getExpression();
if (expr != null) { if (expr != null) {
space(); space();
accept(expr); accept(expr);
} }
popSourceInfo();
} }
@Override @Override
public void visitString(@NotNull JsStringLiteral x) { public void visitString(@NotNull JsStringLiteral x) {
pushSourceInfo(x.getSource());
p.print(javaScriptString(x.getValue())); p.print(javaScriptString(x.getValue()));
popSourceInfo();
} }
@Override @Override
public void visit(@NotNull JsSwitch x) { public void visit(@NotNull JsSwitch x) {
pushSourceInfo(x.getSource());
p.print(CHARS_SWITCH); p.print(CHARS_SWITCH);
spaceOpt(); spaceOpt();
leftParen(); leftParen();
accept(x.getExpression()); accept(x.getExpression());
rightParen(); rightParen();
popSourceInfo();
sourceLocationConsumer.pushSourceInfo(null);
spaceOpt(); spaceOpt();
blockOpen(); blockOpen();
acceptList(x.getCases()); acceptList(x.getCases());
blockClose(); blockClose();
sourceLocationConsumer.popSourceInfo();
} }
@Override @Override
public void visitThis(@NotNull JsThisRef x) { public void visitThis(@NotNull JsThisRef x) {
pushSourceInfo(x.getSource());
p.print(CHARS_THIS); p.print(CHARS_THIS);
popSourceInfo();
} }
@Override @Override
public void visitThrow(@NotNull JsThrow x) { public void visitThrow(@NotNull JsThrow x) {
pushSourceInfo(x.getSource());
p.print(CHARS_THROW); p.print(CHARS_THROW);
space(); space();
accept(x.getExpression()); accept(x.getExpression());
popSourceInfo();
} }
@Override @Override
@@ -798,6 +986,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
@Override @Override
public void visit(@NotNull JsVar var) { public void visit(@NotNull JsVar var) {
pushSourceInfo(var.getSource());
nameOf(var); nameOf(var);
JsExpression initExpr = var.getInitExpression(); JsExpression initExpr = var.getInitExpression();
if (initExpr != null) { if (initExpr != null) {
@@ -810,10 +1000,14 @@ public class JsToStringGenerationVisitor extends JsVisitor {
rightParen(); rightParen();
} }
} }
popSourceInfo();
} }
@Override @Override
public void visitVars(@NotNull JsVars vars) { public void visitVars(@NotNull JsVars vars) {
pushSourceInfo(vars.getSource());
var(); var();
space(); space();
boolean sep = false; boolean sep = false;
@@ -831,6 +1025,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
accept(var); accept(var);
} }
popSourceInfo();
} }
@Override @Override
@@ -844,13 +1040,13 @@ public class JsToStringGenerationVisitor extends JsVisitor {
space(); space();
} }
else { else {
p.newline(); newline();
} }
boolean notFirst = false; boolean notFirst = false;
for (Map.Entry<String, Object> entry : comment.getTags().entrySet()) { for (Map.Entry<String, Object> entry : comment.getTags().entrySet()) {
if (notFirst) { if (notFirst) {
p.newline(); newline();
p.print(' '); p.print(' ');
p.print('*'); p.print('*');
} }
@@ -872,7 +1068,7 @@ public class JsToStringGenerationVisitor extends JsVisitor {
} }
if (!asSingleLine) { if (!asSingleLine) {
p.newline(); newline();
} }
} }
@@ -890,18 +1086,39 @@ public class JsToStringGenerationVisitor extends JsVisitor {
} }
} }
protected final void newlineOpt() { private void newlineOpt() {
if (!p.isCompact()) { if (!p.isCompact()) {
p.newline(); newline();
} }
} }
protected void printJsBlock(JsBlock x, boolean finalNewline) { private void newline() {
p.newline();
sourceLocationConsumer.newLine();
}
private void pushSourceInfo(Object location) {
p.maybeIndent();
sourceInfoStack.add(location);
if (location != null) {
sourceLocationConsumer.pushSourceInfo(location);
}
}
private void popSourceInfo() {
if (!sourceInfoStack.isEmpty() && sourceInfoStack.remove(sourceInfoStack.size() - 1) != null) {
sourceLocationConsumer.popSourceInfo();
}
}
private void printJsBlock(JsBlock x, boolean finalNewline) {
if (!lineBreakAfterBlock) { if (!lineBreakAfterBlock) {
finalNewline = false; finalNewline = false;
lineBreakAfterBlock = true; lineBreakAfterBlock = true;
} }
sourceLocationConsumer.pushSourceInfo(null);
boolean needBraces = !x.isGlobalBlock(); boolean needBraces = !x.isGlobalBlock();
if (needBraces) { if (needBraces) {
blockOpen(); blockOpen();
@@ -949,7 +1166,7 @@ public class JsToStringGenerationVisitor extends JsVisitor {
newlineOpt(); newlineOpt();
} }
else { else {
p.newline(); newline();
} }
} }
else { else {
@@ -973,6 +1190,8 @@ public class JsToStringGenerationVisitor extends JsVisitor {
} }
} }
needSemi = false; needSemi = false;
sourceLocationConsumer.popSourceInfo();
} }
private void assignment() { private void assignment() {
@@ -0,0 +1,25 @@
/*
* 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.backend
object NoOpSourceLocationConsumer : SourceLocationConsumer {
override fun newLine() {}
override fun pushSourceInfo(info: Any?) {}
override fun popSourceInfo() {}
}
@@ -0,0 +1,25 @@
/*
* 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.backend
interface SourceLocationConsumer {
fun newLine()
fun pushSourceInfo(info: Any?)
fun popSourceInfo()
}
@@ -32,21 +32,7 @@ public interface TextOutput {
void printOpt(char c); void printOpt(char c);
void printOpt(char[] s);
void printOpt(String s);
boolean isCompact(); boolean isCompact();
boolean isJustNewlined();
void setOutListener(OutListener outListener);
void maybeIndent(); void maybeIndent();
public interface OutListener {
void newLined();
void indentedAfterNewLine();
}
} }
@@ -17,8 +17,6 @@ public class TextOutputImpl implements TextOutput {
private int line = 0; private int line = 0;
private int column = 0; private int column = 0;
private OutListener outListener;
public TextOutputImpl() { public TextOutputImpl() {
this(false); this(false);
} }
@@ -78,9 +76,6 @@ public class TextOutputImpl implements TextOutput {
line++; line++;
column = 0; column = 0;
justNewlined = true; justNewlined = true;
if (outListener != null) {
outListener.newLined();
}
} }
@Override @Override
@@ -151,9 +146,6 @@ public class TextOutputImpl implements TextOutput {
if (justNewlined && !compact) { if (justNewlined && !compact) {
printAndCount(indents[identLevel]); printAndCount(indents[identLevel]);
justNewlined = false; justNewlined = false;
if (outListener != null) {
outListener.indentedAfterNewLine();
}
} }
} }
@@ -173,9 +165,4 @@ public class TextOutputImpl implements TextOutput {
public boolean isJustNewlined() { public boolean isJustNewlined() {
return justNewlined && !compact; return justNewlined && !compact;
} }
@Override
public void setOutListener(OutListener outListener) {
this.outListener = outListener;
}
} }
@@ -194,7 +194,7 @@ class FunctionReader(
val sourceMap = info.sourceMap val sourceMap = info.sourceMap
if (sourceMap != null) { if (sourceMap != null) {
val remapper = SourceMapLocationRemapper(mapOf(info.filePath to sourceMap)) val remapper = SourceMapLocationRemapper(sourceMap)
remapper.remap(function) remapper.remap(function)
} }
@@ -1115,7 +1115,10 @@ public class JsAstMapper {
astNode.setSource(jsLocation); astNode.setSource(jsLocation);
} }
else if (astNode instanceof JsExpressionStatement) { else if (astNode instanceof JsExpressionStatement) {
((JsExpressionStatement) astNode).getExpression().setSource(jsLocation); JsExpression expression = ((JsExpressionStatement) astNode).getExpression();
if (expression.getSource() == null) {
expression.setSource(jsLocation);
}
} }
} }
return astNode; return astNode;
@@ -180,7 +180,7 @@ public class Parser extends Observable {
// by '(', assume <name> starts memberExpr // by '(', assume <name> starts memberExpr
Node memberExprHead = nameNode; Node memberExprHead = nameNode;
nameNode = null; nameNode = null;
memberExprNode = memberExprTail(ts, false, memberExprHead, basePosition); memberExprNode = memberExprTail(ts, false, memberExprHead);
} }
mustMatchToken(ts, TokenStream.LP, "msg.no.paren.parms"); 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 { private Node expr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = assignExpr(ts, inForInit); Node pn = assignExpr(ts, inForInit);
while (ts.matchToken(TokenStream.COMMA)) { while (ts.matchToken(TokenStream.COMMA)) {
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.COMMA, pn, assignExpr(ts, inForInit), position); pn = nf.createBinary(TokenStream.COMMA, pn, assignExpr(ts, inForInit), position);
} }
return pn; return pn;
} }
private Node assignExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { private Node assignExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = condExpr(ts, inForInit); Node pn = condExpr(ts, inForInit);
if (ts.matchToken(TokenStream.ASSIGN)) { if (ts.matchToken(TokenStream.ASSIGN)) {
// omitted: "invalid assignment left-hand side" check. // omitted: "invalid assignment left-hand side" check.
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit), position); 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 { private Node condExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = orExpr(ts, inForInit); Node pn = orExpr(ts, inForInit);
if (ts.matchToken(TokenStream.HOOK)) { if (ts.matchToken(TokenStream.HOOK)) {
CodePosition position = ts.tokenPosition;
Node ifTrue = assignExpr(ts, false); Node ifTrue = assignExpr(ts, false);
mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.cond"); mustMatchToken(ts, TokenStream.COLON, "msg.no.colon.cond");
Node ifFalse = assignExpr(ts, inForInit); Node ifFalse = assignExpr(ts, inForInit);
@@ -743,9 +743,9 @@ public class Parser extends Observable {
} }
private Node orExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { private Node orExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = andExpr(ts, inForInit); Node pn = andExpr(ts, inForInit);
while (ts.matchToken(TokenStream.OR)) { while (ts.matchToken(TokenStream.OR)) {
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.OR, pn, andExpr(ts, inForInit), position); 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 { private Node andExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = bitOrExpr(ts, inForInit); Node pn = bitOrExpr(ts, inForInit);
while (ts.matchToken(TokenStream.AND)) { while (ts.matchToken(TokenStream.AND)) {
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.AND, pn, bitOrExpr(ts, inForInit), position); 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 { private Node bitOrExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = bitXorExpr(ts, inForInit); Node pn = bitXorExpr(ts, inForInit);
while (ts.matchToken(TokenStream.BITOR)) { while (ts.matchToken(TokenStream.BITOR)) {
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.BITOR, pn, bitXorExpr(ts, inForInit), position); pn = nf.createBinary(TokenStream.BITOR, pn, bitXorExpr(ts, inForInit), position);
} }
return pn; return pn;
} }
private Node bitXorExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { private Node bitXorExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = bitAndExpr(ts, inForInit); Node pn = bitAndExpr(ts, inForInit);
while (ts.matchToken(TokenStream.BITXOR)) { while (ts.matchToken(TokenStream.BITXOR)) {
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.BITXOR, pn, bitAndExpr(ts, inForInit), position); pn = nf.createBinary(TokenStream.BITXOR, pn, bitAndExpr(ts, inForInit), position);
} }
return pn; return pn;
} }
private Node bitAndExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { private Node bitAndExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = eqExpr(ts, inForInit); Node pn = eqExpr(ts, inForInit);
while (ts.matchToken(TokenStream.BITAND)) { while (ts.matchToken(TokenStream.BITAND)) {
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.BITAND, pn, eqExpr(ts, inForInit), position); pn = nf.createBinary(TokenStream.BITAND, pn, eqExpr(ts, inForInit), position);
} }
return pn; return pn;
} }
private Node eqExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { private Node eqExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = relExpr(ts, inForInit); Node pn = relExpr(ts, inForInit);
while (ts.matchToken(TokenStream.EQOP)) { while (ts.matchToken(TokenStream.EQOP)) {
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.EQOP, ts.getOp(), pn, relExpr(ts, inForInit), position); pn = nf.createBinary(TokenStream.EQOP, ts.getOp(), pn, relExpr(ts, inForInit), position);
} }
return pn; return pn;
} }
private Node relExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException { private Node relExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = shiftExpr(ts); Node pn = shiftExpr(ts);
CodePosition position = ts.tokenPosition;
while (ts.matchToken(TokenStream.RELOP)) { while (ts.matchToken(TokenStream.RELOP)) {
int op = ts.getOp(); int op = ts.getOp();
if (inForInit && op == TokenStream.IN) { if (inForInit && op == TokenStream.IN) {
@@ -809,26 +811,26 @@ public class Parser extends Observable {
} }
pn = nf.createBinary(TokenStream.RELOP, op, pn, shiftExpr(ts), position); pn = nf.createBinary(TokenStream.RELOP, op, pn, shiftExpr(ts), position);
position = ts.tokenPosition;
} }
return pn; return pn;
} }
private Node shiftExpr(TokenStream ts) throws IOException, JavaScriptException { private Node shiftExpr(TokenStream ts) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
Node pn = addExpr(ts); Node pn = addExpr(ts);
while (ts.matchToken(TokenStream.SHOP)) { while (ts.matchToken(TokenStream.SHOP)) {
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(TokenStream.SHOP, ts.getOp(), pn, addExpr(ts), position); pn = nf.createBinary(TokenStream.SHOP, ts.getOp(), pn, addExpr(ts), position);
} }
return pn; return pn;
} }
private Node addExpr(TokenStream ts) throws IOException, JavaScriptException { private Node addExpr(TokenStream ts) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
int tt; int tt;
Node pn = mulExpr(ts); Node pn = mulExpr(ts);
while ((tt = ts.getToken()) == TokenStream.ADD || tt == TokenStream.SUB) { while ((tt = ts.getToken()) == TokenStream.ADD || tt == TokenStream.SUB) {
// flushNewLines CodePosition position = ts.tokenPosition;
pn = nf.createBinary(tt, pn, mulExpr(ts), position); pn = nf.createBinary(tt, pn, mulExpr(ts), position);
} }
ts.ungetToken(tt); ts.ungetToken(tt);
@@ -837,13 +839,13 @@ public class Parser extends Observable {
} }
private Node mulExpr(TokenStream ts) throws IOException, JavaScriptException { private Node mulExpr(TokenStream ts) throws IOException, JavaScriptException {
CodePosition position = getNextTokenPosition(ts);
int tt; int tt;
Node pn = unaryExpr(ts); Node pn = unaryExpr(ts);
while ((tt = ts.peekToken()) == TokenStream.MUL || tt == TokenStream.DIV || tt == TokenStream.MOD) { while ((tt = ts.peekToken()) == TokenStream.MUL || tt == TokenStream.DIV || tt == TokenStream.MOD) {
tt = ts.getToken(); tt = ts.getToken();
CodePosition position = ts.tokenPosition;
pn = nf.createBinary(tt, pn, unaryExpr(ts), position); 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) if (((peeked = ts.peekToken()) == TokenStream.INC || peeked == TokenStream.DEC)
&& ts.getLineno() == lineno) { && ts.getLineno() == lineno) {
int pf = ts.getToken(); int pf = ts.getToken();
position = ts.tokenPosition;
return nf.createUnary(pf, TokenStream.POST, pn, position); return nf.createUnary(pf, TokenStream.POST, pn, position);
} }
return pn; return pn;
@@ -960,16 +963,17 @@ public class Parser extends Observable {
pn = primaryExpr(ts); pn = primaryExpr(ts);
} }
return memberExprTail(ts, allowCallSyntax, pn, position); return memberExprTail(ts, allowCallSyntax, pn);
} }
private Node memberExprTail( private Node memberExprTail(
TokenStream ts, boolean allowCallSyntax, TokenStream ts, boolean allowCallSyntax,
Node pn, CodePosition position Node pn
) throws IOException, JavaScriptException { ) throws IOException, JavaScriptException {
lastExprEndLine = ts.getLineno(); lastExprEndLine = ts.getLineno();
int tt; int tt;
while ((tt = ts.getToken()) > TokenStream.EOF) { while ((tt = ts.getToken()) > TokenStream.EOF) {
CodePosition position = ts.tokenPosition;
if (tt == TokenStream.DOT) { if (tt == TokenStream.DOT) {
ts.treatKeywordAsIdentifier = true; ts.treatKeywordAsIdentifier = true;
mustMatchToken(ts, TokenStream.NAME, "msg.no.name.after.dot"); mustMatchToken(ts, TokenStream.NAME, "msg.no.name.after.dot");
@@ -1144,13 +1148,6 @@ public class Parser extends Observable {
return null; // should never reach here 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 int lastExprEndLine; // Hack to handle function expr termination.
private final IRFactory nf; private final IRFactory nf;
private boolean ok; // Did the parse encounter an error? private boolean ok; // Did the parse encounter an error?
@@ -16,15 +16,26 @@
package org.jetbrains.kotlin.js.parser.sourcemaps package org.jetbrains.kotlin.js.parser.sourcemaps
import java.io.PrintStream
import java.io.Reader import java.io.Reader
class SourceMap(val sourceContentResolver: (String) -> Reader?) { class SourceMap(val sourceContentResolver: (String) -> Reader?) {
val groups = mutableListOf<SourceMapGroup>() 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( class SourceMapSegment(
val generatedColumnNumber: Int, val generatedColumnNumber: Int,
val sourceFileName: String, val sourceFileName: String?,
val sourceLineNumber: Int, val sourceLineNumber: Int,
val sourceColumnNumber: Int val sourceColumnNumber: Int
) )
@@ -18,51 +18,118 @@ package org.jetbrains.kotlin.js.parser.sourcemaps
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
class SourceMapLocationRemapper(val sourceMaps: Map<String, SourceMap>) { class SourceMapLocationRemapper(private val sourceMap: SourceMap) {
fun remap(node: JsNode) { fun remap(node: JsNode) {
node.accept(visitor) val listCollector = JsNodeFlatListCollector()
node.accept(listCollector)
applySourceMap(listCollector.nodeList)
} }
private val visitor = object : RecursiveJsVisitor() { private fun applySourceMap(nodes: List<JsNode>) {
private var lastSourceMap: SourceMap? = null var lastGroup: SourceMapGroup? = null
private var lastGroup: SourceMapGroup? = null var lastGroupIndex = 0
private var lastSegmentIndex = 0 var lastSegment: SourceMapSegment? = null
var lastSegmentIndex = 0
override fun visitElement(node: JsNode) { fun findCorrespondingSegment(node: SourceInfoAwareJsNode): SourceMapSegment? {
if (node is SourceInfoAwareJsNode) { val source = node.source as? JsLocation ?: return null
if (!remapNode(node)) { val group = sourceMap.groups.getOrElse(source.startLine) { return null }
node.source = 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 for (node in nodes.asSequence().filterIsInstance<SourceInfoAwareJsNode>()) {
val group = sourceMap.groups.getOrElse(source.startLine) { return false } val segment = findCorrespondingSegment(node)
if (group.segments.isEmpty()) return false val sourceFileName = segment?.sourceFileName
node.source = if (sourceFileName != null) {
if (lastSourceMap != sourceMap || lastGroup != group) { val location = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber)
lastSegmentIndex = 0 JsLocationWithEmbeddedSource(location, sourceMap) { sourceMap.sourceContentResolver(segment.sourceFileName) }
} }
if (group.segments[lastSegmentIndex].generatedColumnNumber > source.startChar) { else {
if (lastSegmentIndex == 0) return false null
lastSegmentIndex = 0
} }
}
}
while (lastSegmentIndex + 1 < group.segments.size) { internal class JsNodeFlatListCollector : RecursiveJsVisitor() {
val nextIndex = lastSegmentIndex + 1 val nodeList = mutableListOf<JsNode>()
if (group.segments[nextIndex].generatedColumnNumber > source.startChar) break
lastSegmentIndex = nextIndex 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] private fun JsNode.isNotBefore(other: JsNode): Boolean {
val location = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber) val first = (source as? JsLocation ?: return false)
node.source = JsLocationWithEmbeddedSource(location, sourceMap) { sourceMap.sourceContentResolver(segment.sourceFileName) } val second = (other.source as? JsLocation ?: return false)
if (first.file != second.file) return false
return true return first.startLine > second.startLine || (first.startLine == second.startLine && first.startChar >= second.startChar)
} }
} }
} }
@@ -14,22 +14,6 @@
* limitations under the License. * 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 package org.jetbrains.kotlin.js.parser.sourcemaps
import org.json.JSONArray import org.json.JSONArray
@@ -115,13 +99,15 @@ object SourceMapParser {
if (stream.isEncodedInt) { if (stream.isEncodedInt) {
stream.readInt() ?: return stream.createError("VLQ-encoded name index expected") stream.readInt() ?: return stream.createError("VLQ-encoded name index expected")
} }
}
if (sourceIndex !in sources.indices) { if (sourceIndex !in sources.indices) {
return stream.createError("Source index $sourceIndex is out of bounds ${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 { when {
stream.isEof -> return stream.createError("Unexpected EOF, ',' or ';' expected") stream.isEof -> return stream.createError("Unexpected EOF, ',' or ';' expected")
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.js.JavaScript import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.config.EcmaVersion import org.jetbrains.kotlin.js.config.EcmaVersion
import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JSConfigurationKeys
@@ -42,7 +43,6 @@ import org.jetbrains.kotlin.js.dce.InputFile
import org.jetbrains.kotlin.js.facade.* import org.jetbrains.kotlin.js.facade.*
import org.jetbrains.kotlin.js.parser.parse import org.jetbrains.kotlin.js.parser.parse
import org.jetbrains.kotlin.js.parser.sourcemaps.* import org.jetbrains.kotlin.js.parser.sourcemaps.*
import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.js.test.utils.* import org.jetbrains.kotlin.js.test.utils.*
@@ -430,8 +430,8 @@ abstract class BasicBoxTest(
val output = TextOutputImpl() val output = TextOutputImpl()
val pathResolver = SourceFilePathResolver(mutableListOf(File("."))) val pathResolver = SourceFilePathResolver(mutableListOf(File(".")))
val sourceMapBuilder = SourceMap3Builder(outputFile, output, "", SourceMapBuilderConsumer(pathResolver, false, false)) val sourceMapBuilder = SourceMap3Builder(outputFile, output, "")
generatedProgram.accept(JsSourceGenerationVisitor(output, sourceMapBuilder)) generatedProgram.accept(JsToStringGenerationVisitor(output, SourceMapBuilderConsumer(sourceMapBuilder, pathResolver, false, false)))
val code = output.toString() val code = output.toString()
val generatedSourceMap = sourceMapBuilder.build() val generatedSourceMap = sourceMapBuilder.build()
@@ -446,7 +446,7 @@ abstract class BasicBoxTest(
is SourceMapError -> error("Could not parse source map: ${sourceMapParseResult.message}") is SourceMapError -> error("Could not parse source map: ${sourceMapParseResult.message}")
} }
val remapper = SourceMapLocationRemapper(mapOf(outputFile.path to sourceMap)) val remapper = SourceMapLocationRemapper(sourceMap)
remapper.remap(parsedProgram) remapper.remap(parsedProgram)
val codeWithRemappedLines = parsedProgram.toStringWithLineNumbers() val codeWithRemappedLines = parsedProgram.toStringWithLineNumbers()
@@ -16,15 +16,14 @@
package org.jetbrains.kotlin.js.test.optimizer package org.jetbrains.kotlin.js.test.optimizer
import com.google.gwt.dev.js.rhino.CodePosition import com.google.gwt.dev.js.rhino.CodePosition
import com.google.gwt.dev.js.rhino.ErrorReporter import com.google.gwt.dev.js.rhino.ErrorReporter
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor
import org.jetbrains.kotlin.js.parser.parse import org.jetbrains.kotlin.js.parser.parse
import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor
import org.jetbrains.kotlin.js.test.BasicBoxTest import org.jetbrains.kotlin.js.test.BasicBoxTest
import org.jetbrains.kotlin.js.test.createScriptEngine import org.jetbrains.kotlin.js.test.createScriptEngine
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
@@ -120,7 +119,7 @@ abstract class BasicOptimizerTest(private var basePath: String) {
private fun astToString(ast: List<JsStatement>): String { private fun astToString(ast: List<JsStatement>): String {
val output = TextOutputImpl() val output = TextOutputImpl()
val visitor = JsSourceGenerationVisitor(output, null) val visitor = JsToStringGenerationVisitor(output)
for (stmt in ast) { for (stmt in ast) {
stmt.accept(visitor) stmt.accept(visitor)
} }
@@ -16,64 +16,20 @@
package org.jetbrains.kotlin.js.test.utils package org.jetbrains.kotlin.js.test.utils
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
class AmbiguousAstSourcePropagation : RecursiveJsVisitor() { class AmbiguousAstSourcePropagation : RecursiveJsVisitor() {
private var sourceDefined = false private var lastSource: Any? = null
override fun visitConditional(x: JsConditional) = acceptAll(x, x.testExpression, x.thenExpression, x.elseExpression)
override fun visitBinaryExpression(x: JsBinaryOperation) = acceptAll(x, x.arg1, x.arg2)
override fun visitPostfixOperation(x: JsPostfixOperation) = acceptAll(x, x.arg)
override fun visitArrayAccess(x: JsArrayAccess) = acceptAll(x, x.arrayExpression, x.indexExpression)
override fun visitPropertyInitializer(x: JsPropertyInitializer) = acceptAll(x, x.labelExpr, x.valueExpr)
override fun visitInvocation(invocation: JsInvocation) = acceptAll(invocation, invocation.qualifier,
*invocation.arguments.toTypedArray())
override fun visitNameRef(nameRef: JsNameRef) {
val qualifier = nameRef.qualifier
if (qualifier != null) {
acceptAll(nameRef, qualifier)
}
else {
super.visitNameRef(nameRef)
}
}
override fun visitElement(node: JsNode) { override fun visitElement(node: JsNode) {
val old = sourceDefined val source = node.source
propagate(node) if (source == null && node is JsExpression) {
node.source = lastSource
}
sourceDefined = false val oldLastSource = lastSource
lastSource = node.source
super.visitElement(node) super.visitElement(node)
sourceDefined = old lastSource = oldLastSource
}
private fun acceptAll(node: JsNode, first: JsNode, vararg remaining: JsNode) {
val old = sourceDefined
propagate(node)
accept(first)
sourceDefined = false
remaining.forEach { accept(it) }
sourceDefined = old
}
private fun propagate(node: JsNode) {
if (!sourceDefined) {
val source = node.source
if (source is JsLocationWithSource || source is PsiElement) {
sourceDefined = true
}
}
else if (node !is JsExpressionStatement) {
node.source = null
}
} }
} }
@@ -87,10 +87,9 @@ class LineCollector : RecursiveJsVisitor() {
override fun visitDoWhile(x: JsDoWhile) { override fun visitDoWhile(x: JsDoWhile) {
withStatement(x) { withStatement(x) {
handleNodeLocation(x) x.body.accept(this)
x.condition.accept(this) x.condition.accept(this)
} }
x.body.accept(this)
} }
override fun visitFor(x: JsFor) { override fun visitFor(x: JsFor) {
@@ -130,8 +129,8 @@ class LineCollector : RecursiveJsVisitor() {
override fun visit(x: JsSwitch) { override fun visit(x: JsSwitch) {
withStatement(x) { withStatement(x) {
x.expression.accept(this) x.expression.accept(this)
x.cases.forEach { accept(it) }
} }
x.cases.forEach { accept(it) }
} }
override fun visitThrow(x: JsThrow) { override fun visitThrow(x: JsThrow) {
@@ -140,6 +139,14 @@ class LineCollector : RecursiveJsVisitor() {
} }
} }
override fun visitTry(x: JsTry) {
withStatement(x) {
x.tryBlock.acceptChildren(this)
x.catches?.forEach { accept(it) }
x.finallyBlock?.acceptChildren(this)
}
}
private fun withStatement(statement: JsStatement, action: () -> Unit) { private fun withStatement(statement: JsStatement, action: () -> Unit) {
val oldStatement = currentStatement val oldStatement = currentStatement
currentStatement = statement currentStatement = statement
@@ -73,6 +73,11 @@ class LineOutputToStringVisitor(output: TextOutput, val lineCollector: LineColle
super.visitReturn(x) super.visitReturn(x)
} }
override fun visitTry(x: JsTry) {
printLineNumbers(x)
super.visitTry(x)
}
override fun visit(x: JsSwitch) { override fun visit(x: JsSwitch) {
printLineNumbers(x) printLineNumbers(x)
super.visit(x) super.visit(x)
@@ -19,17 +19,24 @@ package org.jetbrains.kotlin.js.facade;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.util.PairConsumer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.js.backend.SourceLocationConsumer;
import org.jetbrains.kotlin.js.backend.ast.JsLocation;
import org.jetbrains.kotlin.js.backend.ast.JsLocationWithSource; import org.jetbrains.kotlin.js.backend.ast.JsLocationWithSource;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver; import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder; import org.jetbrains.kotlin.js.sourceMap.SourceMapMappingConsumer;
import java.io.*; import java.io.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
public class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder, Object> { public class SourceMapBuilderConsumer implements SourceLocationConsumer {
@NotNull
private final SourceMapMappingConsumer mappingConsumer;
@NotNull @NotNull
private final SourceFilePathResolver pathResolver; private final SourceFilePathResolver pathResolver;
@@ -37,25 +44,50 @@ public class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder,
private final boolean provideExternalModuleContent; private final boolean provideExternalModuleContent;
@NotNull
private final List<Object> sourceStack = new ArrayList<>();
public SourceMapBuilderConsumer( public SourceMapBuilderConsumer(
@NotNull SourceMapMappingConsumer mappingConsumer,
@NotNull SourceFilePathResolver pathResolver, @NotNull SourceFilePathResolver pathResolver,
boolean provideCurrentModuleContent, boolean provideExternalModuleContent boolean provideCurrentModuleContent, boolean provideExternalModuleContent
) { ) {
this.mappingConsumer = mappingConsumer;
this.pathResolver = pathResolver; this.pathResolver = pathResolver;
this.provideCurrentModuleContent = provideCurrentModuleContent; this.provideCurrentModuleContent = provideCurrentModuleContent;
this.provideExternalModuleContent = provideExternalModuleContent; this.provideExternalModuleContent = provideExternalModuleContent;
} }
@Override @Override
public void consume(SourceMapBuilder builder, Object sourceInfo) { public void newLine() {
mappingConsumer.newLine();
}
@Override
public void pushSourceInfo(@Nullable Object info) {
sourceStack.add(info);
addMapping(info);
}
@Override
public void popSourceInfo() {
sourceStack.remove(sourceStack.size() - 1);
Object sourceInfo = !sourceStack.isEmpty() ? sourceStack.get(sourceStack.size() - 1) : null;
addMapping(sourceInfo);
}
private void addMapping(@Nullable Object sourceInfo) {
if (sourceInfo == null) {
mappingConsumer.addEmptyMapping();
}
if (sourceInfo instanceof PsiElement) { if (sourceInfo instanceof PsiElement) {
PsiElement element = (PsiElement) sourceInfo; PsiElement element = (PsiElement) sourceInfo;
PsiFile psiFile = element.getContainingFile(); PsiFile psiFile = element.getContainingFile();
int offset = element.getNode().getStartOffset(); int offset = element.getNode().getStartOffset();
Document document = psiFile.getViewProvider().getDocument(); Document document = psiFile.getViewProvider().getDocument();
assert document != null; assert document != null;
int line = document.getLineNumber(offset); int sourceLine = document.getLineNumber(offset);
int column = offset - document.getLineStartOffset(line); int sourceColumn = offset - document.getLineStartOffset(sourceLine);
File file = new File(psiFile.getViewProvider().getVirtualFile().getPath()); File file = new File(psiFile.getViewProvider().getVirtualFile().getPath());
try { try {
@@ -73,7 +105,8 @@ public class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder,
else { else {
contentSupplier = () -> null; contentSupplier = () -> null;
} }
builder.addMapping(pathResolver.getPathRelativeToSourceRoots(file), null, contentSupplier, line, column); mappingConsumer.addMapping(pathResolver.getPathRelativeToSourceRoots(file), null, contentSupplier,
sourceLine, sourceColumn);
} }
catch (IOException e) { catch (IOException e) {
throw new RuntimeException("IO error occurred generating source maps", e); throw new RuntimeException("IO error occurred generating source maps", e);
@@ -82,7 +115,7 @@ public class SourceMapBuilderConsumer implements PairConsumer<SourceMapBuilder,
else if (sourceInfo instanceof JsLocationWithSource) { else if (sourceInfo instanceof JsLocationWithSource) {
JsLocationWithSource location = (JsLocationWithSource) sourceInfo; JsLocationWithSource location = (JsLocationWithSource) sourceInfo;
Supplier<Reader> contentSupplier = provideExternalModuleContent ? location.getSourceProvider()::invoke : () -> null; Supplier<Reader> contentSupplier = provideExternalModuleContent ? location.getSourceProvider()::invoke : () -> null;
builder.addMapping(location.getFile(), location.getIdentityObject(), contentSupplier, mappingConsumer.addMapping(location.getFile(), location.getIdentityObject(), contentSupplier,
location.getStartLine(), location.getStartChar()); location.getStartLine(), location.getStartChar());
} }
} }
@@ -20,14 +20,15 @@ import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VfsUtilCore
import org.jetbrains.kotlin.backend.common.output.* import org.jetbrains.kotlin.backend.common.output.*
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
import org.jetbrains.kotlin.js.backend.NoOpSourceLocationConsumer
import org.jetbrains.kotlin.js.backend.ast.JsProgram import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding
import org.jetbrains.kotlin.js.sourceMap.JsSourceGenerationVisitor
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilder import org.jetbrains.kotlin.js.backend.SourceLocationConsumer
import org.jetbrains.kotlin.js.translate.general.FileTranslationResult import org.jetbrains.kotlin.js.translate.general.FileTranslationResult
import org.jetbrains.kotlin.js.util.TextOutput import org.jetbrains.kotlin.js.util.TextOutput
import org.jetbrains.kotlin.js.util.TextOutputImpl import org.jetbrains.kotlin.js.util.TextOutputImpl
@@ -55,26 +56,37 @@ abstract class TranslationResult protected constructor(val diagnostics: Diagnost
val fileTranslationResults: Map<KtFile, FileTranslationResult> val fileTranslationResults: Map<KtFile, FileTranslationResult>
) : TranslationResult(diagnostics) { ) : TranslationResult(diagnostics) {
@Suppress("unused") // Used in kotlin-web-demo in WebDemoTranslatorFacade @Suppress("unused") // Used in kotlin-web-demo in WebDemoTranslatorFacade
fun getCode(): String = getCode(TextOutputImpl(), sourceMapBuilder = null) fun getCode(): String {
val output = TextOutputImpl()
getCode(output, sourceLocationConsumer = null)
return output.toString()
}
fun getOutputFiles(outputFile: File, outputPrefixFile: File?, outputPostfixFile: File?): OutputFileCollection { fun getOutputFiles(outputFile: File, outputPrefixFile: File?, outputPostfixFile: File?): OutputFileCollection {
val output = TextOutputImpl() val output = TextOutputImpl()
val sourceMapBuilder =
val sourceMapBuilder = SourceMap3Builder(outputFile, output, config.sourceMapPrefix)
val sourceMapBuilderConsumer =
if (config.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) { if (config.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) {
val sourceRoots = config.sourceMapRoots.map { File(it) } val sourceRoots = config.sourceMapRoots.map { File(it) }
val sourceMapContentEmbedding = config.sourceMapContentEmbedding val sourceMapContentEmbedding = config.sourceMapContentEmbedding
val pathResolver = SourceFilePathResolver(sourceRoots) val pathResolver = SourceFilePathResolver(sourceRoots)
val consumer = SourceMapBuilderConsumer( SourceMapBuilderConsumer(
sourceMapBuilder,
pathResolver, pathResolver,
sourceMapContentEmbedding == SourceMapSourceEmbedding.ALWAYS, sourceMapContentEmbedding == SourceMapSourceEmbedding.ALWAYS,
sourceMapContentEmbedding != SourceMapSourceEmbedding.NEVER) sourceMapContentEmbedding != SourceMapSourceEmbedding.NEVER)
SourceMap3Builder(outputFile, output, config.sourceMapPrefix, consumer)
} }
else { else {
null null
} }
val code = getCode(output, sourceMapBuilder) getCode(output, sourceMapBuilderConsumer)
if (sourceMapBuilderConsumer != null) {
sourceMapBuilder.addLink()
}
val code = output.toString()
val prefix = outputPrefixFile?.readText() ?: "" val prefix = outputPrefixFile?.readText() ?: ""
val postfix = outputPostfixFile?.readText() ?: "" val postfix = outputPostfixFile?.readText() ?: ""
val sourceFiles = files.map { val sourceFiles = files.map {
@@ -108,18 +120,18 @@ abstract class TranslationResult protected constructor(val diagnostics: Diagnost
} }
} }
if (sourceMapBuilder != null) { if (sourceMapBuilderConsumer != null) {
sourceMapBuilder.skipLinesAtBeginning(StringUtil.getLineBreakCount(prefix)) sourceMapBuilder.skipLinesAtBeginning(StringUtil.getLineBreakCount(prefix))
val sourceMapFile = SimpleOutputFile(sourceFiles, sourceMapBuilder.outFile.name, sourceMapBuilder.build()) val sourceMapFile = SimpleOutputFile(sourceFiles, sourceMapBuilder.outFile.name, sourceMapBuilder.build())
outputFiles.add(sourceMapFile) outputFiles.add(sourceMapFile)
sourceMapBuilder.addLink()
} }
return SimpleOutputFileCollection(outputFiles) return SimpleOutputFileCollection(outputFiles)
} }
private fun getCode(output: TextOutput, sourceMapBuilder: SourceMapBuilder?): String { private fun getCode(output: TextOutput, sourceLocationConsumer: SourceLocationConsumer?) {
program.accept(JsSourceGenerationVisitor(output, sourceMapBuilder)) program.accept(JsToStringGenerationVisitor(output, sourceLocationConsumer ?: NoOpSourceLocationConsumer))
return output.toString()
} }
} }
} }
@@ -1,85 +0,0 @@
/*
* 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.sourceMap;
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.util.TextOutput;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class JsSourceGenerationVisitor extends JsToStringGenerationVisitor implements TextOutput.OutListener {
@Nullable
private final SourceMapBuilder sourceMapBuilder;
private final List<Object> pendingSources = new SmartList<>();
public JsSourceGenerationVisitor(TextOutput out, @Nullable SourceMapBuilder sourceMapBuilder) {
super(out);
this.sourceMapBuilder = sourceMapBuilder;
out.setOutListener(this);
}
@Override
public void newLined() {
if (sourceMapBuilder != null) {
sourceMapBuilder.newLine();
}
}
@Override
public void indentedAfterNewLine() {
if (pendingSources.isEmpty()) return;
assert sourceMapBuilder != null;
for (Object source : pendingSources) {
sourceMapBuilder.processSourceInfo(source);
}
pendingSources.clear();
}
@Override
public void accept(JsNode node) {
mapSource(node);
super.accept(node);
}
private void mapSource(JsNode node) {
if (sourceMapBuilder != null) {
Object sourceInfo = node.getSource();
if (sourceInfo != null) {
if (p.isJustNewlined()) {
pendingSources.add(sourceInfo);
}
else {
sourceMapBuilder.processSourceInfo(sourceInfo);
}
}
}
}
@Override
public void visitProgram(@NotNull JsProgram program) {
program.acceptChildren(this);
if (sourceMapBuilder != null) {
sourceMapBuilder.addLink();
}
}
}
@@ -17,11 +17,11 @@
package org.jetbrains.kotlin.js.sourceMap; package org.jetbrains.kotlin.js.sourceMap;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.PairConsumer;
import gnu.trove.TObjectIntHashMap; import gnu.trove.TObjectIntHashMap;
import kotlin.io.TextStreamsKt; import kotlin.io.TextStreamsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor; import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor;
import org.jetbrains.kotlin.js.common.SourceInfo;
import org.jetbrains.kotlin.js.util.TextOutput; import org.jetbrains.kotlin.js.util.TextOutput;
import java.io.File; import java.io.File;
@@ -35,7 +35,6 @@ public class SourceMap3Builder implements SourceMapBuilder {
private final File generatedFile; private final File generatedFile;
private final TextOutput textOutput; private final TextOutput textOutput;
private final String pathPrefix; private final String pathPrefix;
private final PairConsumer<SourceMapBuilder, Object> sourceInfoConsumer;
private final TObjectIntHashMap<SourceKey> sources = new TObjectIntHashMap<SourceKey>() { private final TObjectIntHashMap<SourceKey> sources = new TObjectIntHashMap<SourceKey>() {
@Override @Override
@@ -52,13 +51,16 @@ public class SourceMap3Builder implements SourceMapBuilder {
private int previousSourceIndex; private int previousSourceIndex;
private int previousSourceLine; private int previousSourceLine;
private int previousSourceColumn; private int previousSourceColumn;
private int previousMappingOffset;
private int previousPreviousSourceIndex;
private int previousPreviousSourceLine;
private int previousPreviousSourceColumn;
private boolean currentMappingIsEmpty = true;
public SourceMap3Builder(File generatedFile, TextOutput textOutput, String pathPrefix, public SourceMap3Builder(File generatedFile, TextOutput textOutput, String pathPrefix) {
PairConsumer<SourceMapBuilder, Object> sourceInfoConsumer) {
this.generatedFile = generatedFile; this.generatedFile = generatedFile;
this.textOutput = textOutput; this.textOutput = textOutput;
this.pathPrefix = pathPrefix; this.pathPrefix = pathPrefix;
this.sourceInfoConsumer = sourceInfoConsumer;
} }
@Override @Override
@@ -130,14 +132,6 @@ public class SourceMap3Builder implements SourceMapBuilder {
out.insert(0, StringUtil.repeatSymbol(';', count)); out.insert(0, StringUtil.repeatSymbol(';', count));
} }
@Override
public void processSourceInfo(Object sourceInfo) {
if (sourceInfo instanceof SourceInfo) {
throw new UnsupportedOperationException("SourceInfo is not yet supported");
}
sourceInfoConsumer.consume(this, sourceInfo);
}
private int getSourceIndex(String source, Object identityObject, Supplier<Reader> contentSupplier) { private int getSourceIndex(String source, Object identityObject, Supplier<Reader> contentSupplier) {
SourceKey key = new SourceKey(source, identityObject); SourceKey key = new SourceKey(source, identityObject);
int sourceIndex = sources.get(key); int sourceIndex = sources.get(key);
@@ -152,26 +146,20 @@ public class SourceMap3Builder implements SourceMapBuilder {
} }
@Override @Override
public void addMapping(String source, Object identityObject, Supplier<Reader> sourceContent, int sourceLine, int sourceColumn) { public void addMapping(
@NotNull String source, @Nullable Object identityObject, @NotNull Supplier<Reader> sourceContent,
int sourceLine, int sourceColumn
) {
source = source.replace(File.separatorChar, '/'); source = source.replace(File.separatorChar, '/');
boolean newGroupStarted = previousGeneratedColumn == -1; int sourceIndex = getSourceIndex(source, identityObject, sourceContent);
if (newGroupStarted) {
previousGeneratedColumn = 0;
}
int columnDiff = textOutput.getColumn() - previousGeneratedColumn; if (!currentMappingIsEmpty && previousSourceIndex == sourceIndex && previousSourceLine == sourceLine &&
if (!newGroupStarted && columnDiff == 0) { previousSourceColumn == sourceColumn) {
return; return;
} }
if (!newGroupStarted) {
out.append(',');
}
// TODO fix sections overlapping startMapping();
// assert columnDiff != 0;
Base64VLQ.encode(out, columnDiff);
previousGeneratedColumn = textOutput.getColumn();
int sourceIndex = getSourceIndex(source, identityObject, sourceContent);
Base64VLQ.encode(out, sourceIndex - previousSourceIndex); Base64VLQ.encode(out, sourceIndex - previousSourceIndex);
previousSourceIndex = sourceIndex; previousSourceIndex = sourceIndex;
@@ -180,6 +168,44 @@ public class SourceMap3Builder implements SourceMapBuilder {
Base64VLQ.encode(out, sourceColumn - previousSourceColumn); Base64VLQ.encode(out, sourceColumn - previousSourceColumn);
previousSourceColumn = sourceColumn; previousSourceColumn = sourceColumn;
currentMappingIsEmpty = false;
}
@Override
public void addEmptyMapping() {
if (!currentMappingIsEmpty) {
startMapping();
currentMappingIsEmpty = true;
}
}
private void startMapping() {
boolean newGroupStarted = previousGeneratedColumn == -1;
if (newGroupStarted) {
previousGeneratedColumn = 0;
}
int columnDiff = textOutput.getColumn() - previousGeneratedColumn;
if (!newGroupStarted) {
out.append(',');
}
if (columnDiff > 0 || newGroupStarted) {
Base64VLQ.encode(out, columnDiff);
previousGeneratedColumn = textOutput.getColumn();
previousMappingOffset = out.length();
previousPreviousSourceIndex = previousSourceIndex;
previousPreviousSourceLine = previousSourceLine;
previousPreviousSourceColumn = previousSourceColumn;
}
else {
out.setLength(previousMappingOffset);
previousSourceIndex = previousPreviousSourceIndex;
previousSourceLine = previousPreviousSourceLine;
previousSourceColumn = previousPreviousSourceColumn;
}
} }
@Override @Override
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2015 JetBrains s.r.o. * Copyright 2010-2017 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,18 +17,10 @@
package org.jetbrains.kotlin.js.sourceMap; package org.jetbrains.kotlin.js.sourceMap;
import java.io.File; import java.io.File;
import java.io.Reader;
import java.util.function.Supplier;
public interface SourceMapBuilder {
void newLine();
public interface SourceMapBuilder extends SourceMapMappingConsumer {
void skipLinesAtBeginning(int count); void skipLinesAtBeginning(int count);
void addMapping(String source, Object identityObject, Supplier<Reader> sourceContent, int sourceLine, int sourceColumn);
void processSourceInfo(Object info);
void addLink(); void addLink();
File getOutFile(); File getOutFile();
@@ -0,0 +1,35 @@
/*
* 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.sourceMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.util.function.Supplier;
public interface SourceMapMappingConsumer {
void newLine();
void addMapping(
@NotNull String source, @Nullable Object sourceIdentity, @NotNull Supplier<Reader> sourceSupplier,
int sourceLine, int sourceColumn
);
void addEmptyMapping();
}
@@ -6,4 +6,4 @@ fun box(x: Int, y: Int) {
fun foo(x: Int) = x fun foo(x: Int) = x
// LINES: 3 3 3 4 4 4 4 2 3 7 // LINES: 3 3 * 3 4 4 * 4 4 2 3 7
@@ -13,4 +13,4 @@ fun box() {
) )
} }
// LINES: 8 3 2 3 3 3 8 11 3 7 12 3 3 4 // LINES: 8 3 2 3 3 8 11 * 3 7 12 3 3 4
@@ -6,4 +6,4 @@ enum class Foo {
} }
} }
// LINES: 1 1 1 1 2 4 * 2 2 4 5 * 4 4 4 4 * 1 * 1 1 1 1 // LINES: 1 1 1 1 2 4 * 2 2 4 5 * 4 4 4 4 * 1 * 1 1 1
+1 -1
View File
@@ -10,4 +10,4 @@ enum class E {
} }
} }
// LINES: 1 1 1 1 2 4 8 * 2 2 4 4 5 * 4 4 8 8 9 * 8 8 * 1 * 1 1 1 1 1 1 // LINES: 1 1 1 1 2 4 8 * 2 2 4 4 5 * 4 4 8 8 9 * 8 8 * 1 * 1 1 1 1 1
+1 -1
View File
@@ -21,4 +21,4 @@ fun bar(x: Int) {
println("%") println("%")
} }
// LINES: 2 3 4 7 9 10 11 14 16 * 20 * 2 3 4 3 7 9 10 11 10 14 16 20 21 // LINES: 2 3 4 7 9 10 11 14 16 * 20 * 2 3 4 3 7 9 10 11 10 14 16 * 20 21
@@ -8,4 +8,4 @@ enum class E {
} }
} }
// LINES: 1 1 1 1 2 3 4 * 2 2 * 3 3 4 4 6 * 4 4 * 1 * 1 1 1 1 1 1 // LINES: 1 1 1 1 2 3 4 * 2 2 * 3 3 4 4 6 * 4 4 * 1 * 1 1 1 1 1
@@ -12,4 +12,4 @@ fun box() {
} }
} }
// LINES: 5 2 3 5 8 3 4 9 3 11 // LINES: 5 2 3 5 8 * 3 4 9 3 11
+2 -2
View File
@@ -3,10 +3,10 @@
<head> <head>
<script type="application/javascript" src="../../../dist/js/kotlin.js"></script> <script type="application/javascript" src="../../../dist/js/kotlin.js"></script>
<script type="application/javascript" src="../../../dist/classes/kotlin-test-js/kotlin-test.js"></script> <script type="application/javascript" src="../../../dist/classes/kotlin-test-js/kotlin-test.js"></script>
<script type="application/javascript" src="../../../js/js.translator/testData/out/codegen/box/arrays/primitiveArrays_v5.js"></script> <script type="application/javascript" src="out/box/expression/try/tryCatchExpr_v5.js"></script>
<script type="application/javascript"> <script type="application/javascript">
console.log(JS_TESTS.box()); console.log(JS_TESTS.foo.box());
</script> </script>
</head> </head>
<body> <body>