chore: fix current state of comments in js call.
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsCodeOutliningLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsCodeOutliningLowering.kt
index c1dd2ae..a4e0ab3 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsCodeOutliningLowering.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsCodeOutliningLowering.kt
@@ -11,7 +11,7 @@
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
-import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.translateJsCodeIntoStatementList
+import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.translateJsCodeIntoJsScript
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
@@ -132,7 +132,8 @@
return null
val jsCodeArg = expression.getValueArgument(0) ?: compilationException("Expected js code string", expression)
- val jsStatements = translateJsCodeIntoStatementList(jsCodeArg, backendContext) ?: return null
+ val jsScript = translateJsCodeIntoJsScript(jsCodeArg, backendContext) ?: return null
+ val jsStatements = jsScript.statements
// Collect used Kotlin local variables and parameters.
val scope = JsScopesCollector().apply { acceptList(jsStatements) }
@@ -175,7 +176,13 @@
newStatements += JsReturn(JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(3)))
}
}
- val newFun = JsFunction(emptyScope, JsBlock(newStatements), "")
+
+ val newFun = JsFunction(
+ emptyScope,
+ JsBlock(JsScript(newStatements, jsScript.comments)),
+ ""
+ )
+
kotlinLocalsUsedInJs.forEach { irParameter ->
newFun.parameters.add(JsParameter(JsName(irParameter.name.identifier, false)))
}
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/FunctionWithJsFuncAnnotationInliner.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/FunctionWithJsFuncAnnotationInliner.kt
index 4874a43..11d5817 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/FunctionWithJsFuncAnnotationInliner.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/FunctionWithJsFuncAnnotationInliner.kt
@@ -12,24 +12,33 @@
import org.jetbrains.kotlin.js.backend.ast.*
class FunctionWithJsFuncAnnotationInliner(private val jsFuncCall: IrCall, private val context: JsGenerationContext) {
+ private val script = getJsScript()
private val function = getJsFunctionImplementation()
private val replacements = collectReplacementsForCall()
- fun generateResultStatement(): List<JsStatement> {
+ fun generateResultJsScript(): JsScript {
return function.body.statements
.run {
SimpleJsCodeInliner(replacements)
.apply { acceptList(this@run) }
.withTemporaryVariablesForExpressions(this)
}
+ .run {
+ JsScript(this, script.comments)
+ }
}
private fun getJsFunctionImplementation(): JsFunction {
+ return (script.statements.firstOrNull() as? JsExpressionStatement)
+ ?.let { it.expression as? JsFunction } ?: compilationException("Provided js code is not a js function", jsFuncCall.symbol.owner)
+ }
+
+ private fun getJsScript(): JsScript {
val code = jsFuncCall.symbol.owner.getJsFunAnnotation() ?: compilationException("JsFun annotation is expected", jsFuncCall)
val statements = parseJsCode(code) ?: compilationException("Cannot compute js code", jsFuncCall)
return statements.singleOrNull()
?.let { it as? JsExpressionStatement }
- ?.let { it.expression as? JsFunction } ?: compilationException("Provided js code is not a js function", jsFuncCall.symbol.owner)
+ ?.let { it.expression as? JsScript } ?: compilationException("Provided js code has wrong structure", jsFuncCall.symbol.owner)
}
private fun collectReplacementsForCall(): Map<JsName, JsExpression> {
@@ -42,7 +51,7 @@
}
}
-private class SimpleJsCodeInliner(private val replacements: Map<JsName, JsExpression>): RecursiveJsVisitor() {
+private class SimpleJsCodeInliner(private val replacements: Map<JsName, JsExpression>) : RecursiveJsVisitor() {
private val temporaryNamesForExpressions = mutableMapOf<JsName, JsExpression>()
fun withTemporaryVariablesForExpressions(statements: List<JsStatement>): List<JsStatement> {
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsCallTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsCallTransformer.kt
index 920ff1d..2cb9e93 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsCallTransformer.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsCallTransformer.kt
@@ -12,13 +12,13 @@
import org.jetbrains.kotlin.js.backend.ast.*
class JsCallTransformer(private val jsOrJsFuncCall: IrCall, private val context: JsGenerationContext) {
- private val statements = getJsStatements()
+ private val script = getJsScript()
fun generateStatement(): JsStatement {
- if (statements.isEmpty()) return JsEmpty
+ if (script.statements.isEmpty()) return JsEmpty
- val newStatements = statements.toMutableList().apply {
- val expression = (last() as? JsReturn)?.expression ?: return@apply
+ val newStatements = script.statements.toMutableList().apply {
+ val expression = (script.statements.last() as? JsReturn)?.expression ?: return@apply
if (expression is JsPrefixOperation && expression.operator == JsUnaryOperator.VOID) {
removeLastOrNull()
@@ -27,47 +27,56 @@
}
}
- return when (newStatements.size) {
- 0 -> JsEmpty
- 1 -> newStatements.single().withSource(jsOrJsFuncCall, context)
+ val statements = when (newStatements.size) {
+ 0 -> return JsEmpty
+ 1 -> newStatements.map { it.withSource(jsOrJsFuncCall, context) }
// TODO: use transparent block (e.g. JsCompositeBlock)
- else -> JsBlock(newStatements)
+ else -> newStatements
}
+
+ return JsScript(statements, script.comments)
}
fun generateExpression(): JsExpression {
- if (statements.isEmpty()) return JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(3)) // TODO: report warning or even error
+ if (script.statements.isEmpty()) return JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(3)) // TODO: report warning or even error
- val lastStatement = statements.last()
+ val lastStatement = script.statements.last()
val lastExpression = when (lastStatement) {
is JsReturn -> lastStatement.expression
is JsExpressionStatement -> lastStatement.expression
else -> null
}
- if (statements.size == 1 && lastExpression != null) {
- return lastExpression.withSource(jsOrJsFuncCall, context)
+ if (script.statements.size == 1 && lastExpression != null) {
+ return JsScript(
+ listOf(lastExpression.withSource(jsOrJsFuncCall, context).makeStmt()),
+ script.comments
+ )
}
- val newStatements = statements.toMutableList()
+ val newStatements = script.statements.toMutableList()
when (lastStatement) {
is JsReturn -> {
}
is JsExpressionStatement -> {
- newStatements[statements.lastIndex] = JsReturn(lastStatement.expression)
+ newStatements[script.statements.lastIndex] = JsReturn(lastStatement.expression)
}
// TODO: report warning or even error
else -> newStatements += JsReturn(JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(3)))
}
- val syntheticFunction = JsFunction(emptyScope, JsBlock(newStatements), "")
+ val syntheticFunction = JsFunction(
+ emptyScope,
+ JsBlock(JsScript(newStatements, script.comments)),
+ ""
+ )
return JsInvocation(syntheticFunction).withSource(jsOrJsFuncCall, context)
}
- private fun getJsStatements(): List<JsStatement> {
+ private fun getJsScript(): JsScript {
return when {
context.checkIfJsCode(jsOrJsFuncCall.symbol) -> {
- translateJsCodeIntoStatementList(
+ translateJsCodeIntoJsScript(
jsOrJsFuncCall.getValueArgument(0) ?: compilationException("JsCode is expected", jsOrJsFuncCall),
context.staticContext.backendContext
)
@@ -75,7 +84,7 @@
}
context.checkIfAnnotatedWithJsFunc(jsOrJsFuncCall.symbol) ->
- FunctionWithJsFuncAnnotationInliner(jsOrJsFuncCall, context).generateResultStatement()
+ FunctionWithJsFuncAnnotationInliner(jsOrJsFuncCall, context).generateResultJsScript()
else -> compilationException("`js` function call or function with @JsFunc annotation expected", jsOrJsFuncCall)
}
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsCode.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsCode.kt
index e3ffbfc..ad88ae5 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsCode.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsCode.kt
@@ -15,17 +15,16 @@
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
-import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope
-import org.jetbrains.kotlin.js.backend.ast.JsProgram
-import org.jetbrains.kotlin.js.backend.ast.JsRootScope
-import org.jetbrains.kotlin.js.backend.ast.JsStatement
+import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.parser.parseExpressionOrStatement
// Returns null if constant expression could not be parsed
-fun translateJsCodeIntoStatementList(code: IrExpression, context: JsIrBackendContext): List<JsStatement>? {
+fun translateJsCodeIntoJsScript(code: IrExpression, context: JsIrBackendContext): JsScript? {
// TODO: support proper symbol linkage and label clash resolution
return parseJsCode(foldString(code, context) ?: return null)
+ ?.let { it.firstOrNull() as? JsExpressionStatement }
+ ?.let { it.expression as? JsScript }
}
fun parseJsCode(jsCode: String): List<JsStatement>? {
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsPrecedenceVisitor.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsPrecedenceVisitor.java
index 358d2f8..eb78cf6 100644
--- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsPrecedenceVisitor.java
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsPrecedenceVisitor.java
@@ -46,6 +46,12 @@
}
@Override
+ public void visitScript(@NotNull JsScript x) {
+ JsExpressionStatement statement = (JsExpressionStatement) x.getStatements().get(0);
+ statement.getExpression().accept(this);
+ }
+
+ @Override
public void visitArrayAccess(@NotNull JsArrayAccess x) {
answer = 16;
}
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java
index 6836b37..1630472 100644
--- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/JsToStringGenerationVisitor.java
@@ -54,6 +54,9 @@
@NotNull
private final List<Object> sourceInfoStack = new ArrayList<>();
+ @NotNull
+ private final Queue<JsComment> commentsQueue = new LinkedList<>();
+
public static CharSequence javaScriptString(String value) {
return javaScriptString(value, false);
}
@@ -194,7 +197,16 @@
}
@Override
+ protected void visitElement(@NotNull JsNode node) {
+ if (node instanceof SourceInfoAwareJsNode) {
+ maybePrintComment((SourceInfoAwareJsNode) node);
+ }
+ }
+
+ @Override
public void visitArrayAccess(@NotNull JsArrayAccess x) {
+ super.visitArrayAccess(x);
+
pushSourceInfo(x.getSource());
printPair(x, x.getArrayExpression());
@@ -207,6 +219,8 @@
@Override
public void visitArray(@NotNull JsArrayLiteral x) {
+ super.visitArray(x);
+
pushSourceInfo(x.getSource());
leftSquare();
@@ -230,6 +244,8 @@
@Override
public void visitBinaryExpression(@NotNull JsBinaryOperation binaryOperation) {
+ super.visitBinaryExpression(binaryOperation);
+
pushSourceInfo(binaryOperation.getSource());
JsBinaryOperator operator = binaryOperation.getOperator();
@@ -279,11 +295,13 @@
@Override
public void visitBlock(@NotNull JsBlock x) {
+ super.visitBlock(x);
printJsBlock(x, true, null);
}
@Override
public void visitBoolean(@NotNull JsBooleanLiteral x) {
+ super.visitBoolean(x);
pushSourceInfo(x.getSource());
if (x.getValue()) {
@@ -298,6 +316,7 @@
@Override
public void visitBreak(@NotNull JsBreak x) {
+ super.visitBreak(x);
pushSourceInfo(x.getSource());
p.print(CHARS_BREAK);
@@ -308,6 +327,7 @@
@Override
public void visitContinue(@NotNull JsContinue x) {
+ super.visitContinue(x);
pushSourceInfo(x.getSource());
p.print(CHARS_CONTINUE);
@@ -326,6 +346,7 @@
@Override
public void visitCase(@NotNull JsCase x) {
+ super.visitCase(x);
pushSourceInfo(x.getSource());
p.print(CHARS_CASE);
@@ -358,6 +379,7 @@
@Override
public void visitCatch(@NotNull JsCatch x) {
+ super.visitCatch(x);
pushSourceInfo(x.getSource());
spaceOpt();
@@ -388,6 +410,7 @@
@Override
public void visitConditional(@NotNull JsConditional x) {
+ super.visitConditional(x);
pushSourceInfo(x.getSource());
// Associativity: for the then and else branches, it is safe to insert
@@ -424,6 +447,7 @@
@Override
public void visitDebugger(@NotNull JsDebugger x) {
+ super.visitDebugger(x);
pushSourceInfo(x.getSource());
p.print(CHARS_DEBUGGER);
@@ -433,6 +457,7 @@
@Override
public void visitDefault(@NotNull JsDefault x) {
+ super.visitDefault(x);
pushSourceInfo(x.getSource());
p.print(CHARS_DEFAULT);
@@ -448,6 +473,7 @@
@Override
public void visitWhile(@NotNull JsWhile x) {
+ super.visitWhile(x);
pushSourceInfo(x.getSource());
_while();
@@ -467,6 +493,7 @@
@Override
public void visitDoWhile(@NotNull JsDoWhile x) {
+ super.visitDoWhile(x);
sourceLocationConsumer.pushSourceInfo(null);
p.print(CHARS_DO);
@@ -495,11 +522,8 @@
}
@Override
- public void visitEmpty(@NotNull JsEmpty x) {
- }
-
- @Override
public void visitExpressionStatement(@NotNull JsExpressionStatement x) {
+ super.visitExpressionStatement(x);
Object source = x.getSource();
if (source == null && !(x.getExpression() instanceof JsFunction)) {
source = x.getExpression().getSource();
@@ -520,6 +544,7 @@
@Override
public void visitFor(@NotNull JsFor x) {
+ super.visitFor(x);
pushSourceInfo(x.getSource());
_for();
@@ -568,6 +593,7 @@
@Override
public void visitForIn(@NotNull JsForIn x) {
+ super.visitForIn(x);
pushSourceInfo(x.getSource());
_for();
@@ -610,6 +636,7 @@
@Override
public void visitFunction(@NotNull JsFunction x) {
+ super.visitFunction(x);
pushSourceInfo(x.getSource());
p.print(CHARS_FUNCTION);
@@ -647,6 +674,7 @@
@Override
public void visitClass(@NotNull JsClass x) {
+ super.visitClass(x);
pushSourceInfo(x.getSource());
p.print(CHARS_CLASS);
@@ -693,6 +721,7 @@
@Override
public void visitIf(@NotNull JsIf x) {
+ super.visitIf(x);
pushSourceInfo(x.getSource());
_if();
@@ -759,6 +788,7 @@
@Override
public void visitInvocation(@NotNull JsInvocation invocation) {
+ super.visitInvocation(invocation);
pushSourceInfo(invocation.getSource());
printPair(invocation, invocation.getQualifier());
@@ -772,6 +802,7 @@
@Override
public void visitLabel(@NotNull JsLabel x) {
+ super.visitLabel(x);
nameOf(x);
_colon();
spaceOpt();
@@ -783,6 +814,7 @@
@Override
public void visitNameRef(@NotNull JsNameRef nameRef) {
+ super.visitNameRef(nameRef);
pushSourceInfo(nameRef.getSource());
JsExpression qualifier = nameRef.getQualifier();
@@ -814,6 +846,7 @@
@Override
public void visitNew(@NotNull JsNew x) {
+ super.visitNew(x);
pushSourceInfo(x.getSource());
p.print(CHARS_NEW);
@@ -838,6 +871,7 @@
@Override
public void visitNull(@NotNull JsNullLiteral x) {
+ super.visitNull(x);
pushSourceInfo(x.getSource());
p.print(CHARS_NULL);
@@ -847,6 +881,7 @@
@Override
public void visitInt(@NotNull JsIntLiteral x) {
+ super.visitInt(x);
pushSourceInfo(x.getSource());
p.print(x.value);
@@ -856,6 +891,7 @@
@Override
public void visitDouble(@NotNull JsDoubleLiteral x) {
+ super.visitDouble(x);
pushSourceInfo(x.getSource());
p.print(x.value);
@@ -865,6 +901,7 @@
@Override
public void visitObjectLiteral(@NotNull JsObjectLiteral objectLiteral) {
+ super.visitObjectLiteral(objectLiteral);
pushSourceInfo(objectLiteral.getSource());
p.print('{');
@@ -937,11 +974,13 @@
@Override
public void visitParameter(@NotNull JsParameter x) {
+ super.visitParameter(x);
nameOf(x);
}
@Override
public void visitPostfixOperation(@NotNull JsPostfixOperation x) {
+ super.visitPostfixOperation(x);
pushSourceInfo(x.getSource());
JsUnaryOperator op = x.getOperator();
@@ -955,6 +994,7 @@
@Override
public void visitPrefixOperation(@NotNull JsPrefixOperation x) {
+ super.visitPrefixOperation(x);
pushSourceInfo(x.getSource());
JsUnaryOperator op = x.getOperator();
@@ -971,11 +1011,13 @@
@Override
public void visitProgram(@NotNull JsProgram x) {
+ super.visitProgram(x);
x.acceptChildren(this);
}
@Override
public void visitRegExp(@NotNull JsRegExp x) {
+ super.visitRegExp(x);
pushSourceInfo(x.getSource());
slash();
@@ -991,6 +1033,7 @@
@Override
public void visitReturn(@NotNull JsReturn x) {
+ super.visitReturn(x);
pushSourceInfo(x.getSource());
p.print(CHARS_RETURN);
@@ -1005,6 +1048,7 @@
@Override
public void visitString(@NotNull JsStringLiteral x) {
+ super.visitString(x);
pushSourceInfo(x.getSource());
p.print(javaScriptString(x.getValue()));
@@ -1014,6 +1058,7 @@
@Override
public void visit(@NotNull JsSwitch x) {
+ super.visit(x);
pushSourceInfo(x.getSource());
p.print(CHARS_SWITCH);
@@ -1035,6 +1080,7 @@
@Override
public void visitThis(@NotNull JsThisRef x) {
+ super.visitThis(x);
pushSourceInfo(x.getSource());
p.print(CHARS_THIS);
@@ -1044,6 +1090,7 @@
@Override
public void visitThrow(@NotNull JsThrow x) {
+ super.visitThrow(x);
pushSourceInfo(x.getSource());
p.print(CHARS_THROW);
@@ -1055,6 +1102,7 @@
@Override
public void visitTry(@NotNull JsTry x) {
+ super.visitTry(x);
p.print(CHARS_TRY);
spaceOpt();
lineBreakAfterBlock = false;
@@ -1072,6 +1120,7 @@
@Override
public void visit(@NotNull JsVar var) {
+ super.visit(var);
pushSourceInfo(var.getSource());
nameOf(var);
@@ -1092,6 +1141,7 @@
@Override
public void visitVars(@NotNull JsVars vars) {
+ super.visitVars(vars);
pushSourceInfo(vars.getSource());
var();
@@ -1124,6 +1174,18 @@
}
@Override
+ public void visitMultiLineComment(@NotNull JsMultiLineComment comment) {
+ p.print("/*");
+ p.print(comment.getText());
+ p.print("*/");
+ needSemi = false;
+
+ if (comment.getText().contains("\n")) {
+ newline();
+ }
+ }
+
+ @Override
public void visitDocComment(@NotNull JsDocComment comment) {
boolean asSingleLine = comment.getTags().size() == 1;
if (!asSingleLine) {
@@ -1188,6 +1250,7 @@
@Override
public void visitExport(@NotNull JsExport export) {
+ super.visitExport(export);
p.print("export");
space();
JsExport.Subject subject = export.getSubject();
@@ -1220,6 +1283,7 @@
@Override
public void visitImport(@NotNull JsImport jsImport) {
+ super.visitImport(jsImport);
p.print("import {");
boolean isMultiline = jsImport.getElements().size() > 1;
p.indentIn();
@@ -1248,6 +1312,44 @@
p.print(javaScriptString(jsImport.getModule()));
}
+ @Override
+ public void visitScript(@NotNull JsScript script) {
+ commentsQueue.addAll(script.getComments());
+ acceptList(script.getStatements());
+
+ while (!commentsQueue.isEmpty()) {
+ JsComment comment = commentsQueue.poll();
+ printComment(comment);
+ }
+
+ newline();
+ }
+
+ private void maybePrintComment(SourceInfoAwareJsNode node) {
+ if (commentsQueue.isEmpty()) return;
+ JsComment comment = commentsQueue.peek();
+ JsLocation nodeLocation = (JsLocation) node.getSource();
+ JsLocation commentLocation = (JsLocation) comment.getSource();
+
+ if (nodeLocation == null || commentLocation == null) return;
+
+ if (nodeLocation.compareTo(commentLocation) >= 0) {
+ printComment(commentsQueue.poll());
+ maybePrintComment(comment);
+ }
+
+ }
+
+ private void printComment(JsComment comment) {
+ if (comment instanceof JsSingleLineComment) {
+ visitSingleLineComment((JsSingleLineComment) comment);
+ }
+
+ if (comment instanceof JsMultiLineComment) {
+ visitMultiLineComment((JsMultiLineComment) comment);
+ }
+ }
+
private void newline() {
p.newline();
sourceLocationConsumer.newLine();
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsComment.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsComment.kt
new file mode 100644
index 0000000..f6075ad
--- /dev/null
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsComment.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.js.backend.ast
+
+abstract class JsComment(val text: String) : SourceInfoAwareJsNode(), JsStatement {
+ override fun acceptChildren(visitor: JsVisitor) {}
+
+ override fun deepCopy() = this
+}
\ No newline at end of file
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt
index 4519d9a..6ce6e8d 100644
--- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsLocation.kt
@@ -22,11 +22,18 @@
override val file: String,
override val startLine: Int,
override val startChar: Int
-) : JsLocationWithSource {
+) : JsLocationWithSource, Comparable<JsLocation> {
override val identityObject: Any? = null
override val sourceProvider: () -> Reader? = { null }
override fun asSimpleLocation(): JsLocation = this
+
+ override fun compareTo(other: JsLocation): Int {
+ val linesDiff = startLine - other.startLine
+ if (linesDiff != 0) return linesDiff
+
+ return startChar - other.startChar
+ }
}
interface JsLocationWithSource {
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsMultiLineComment.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsMultiLineComment.kt
new file mode 100644
index 0000000..856ae15
--- /dev/null
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsMultiLineComment.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.js.backend.ast
+
+class JsMultiLineComment(text: String) : JsComment(text) {
+ override fun accept(visitor: JsVisitor) {
+ visitor.visitMultiLineComment(this)
+ }
+
+ override fun traverse(visitor: JsVisitorWithContext, ctx: JsContext<*>) {
+ visitor.visit(this, ctx)
+ visitor.endVisit(this, ctx)
+ }
+}
\ No newline at end of file
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsScript.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsScript.kt
new file mode 100644
index 0000000..bded8a6
--- /dev/null
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsScript.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.js.backend.ast
+
+import org.jetbrains.kotlin.js.util.AstUtil
+
+class JsScript(val statements: List<JsStatement>, val comments: List<JsComment>) : JsStatement, JsExpression() {
+ constructor(comments: List<JsComment>) : this(mutableListOf(), comments)
+
+ override fun deepCopy(): JsScript {
+ return JsScript(AstUtil.deepCopy(statements), comments).withMetadataFrom(this);
+ }
+
+ override fun accept(v: JsVisitor) {
+ v.visitScript(this)
+ }
+
+ override fun acceptChildren(visitor: JsVisitor) {
+ visitor.acceptList(statements)
+ }
+
+ override fun traverse(v: JsVisitorWithContext, ctx: JsContext<*>) {
+ if (v.visit(this, ctx)) {
+ v.acceptStatementList(statements)
+ }
+ v.endVisit(this, ctx)
+ }
+}
\ No newline at end of file
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsSingleLineComment.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsSingleLineComment.kt
index b299b92..ec7fac9 100644
--- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsSingleLineComment.kt
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsSingleLineComment.kt
@@ -5,18 +5,13 @@
package org.jetbrains.kotlin.js.backend.ast
-class JsSingleLineComment(val text: String) : SourceInfoAwareJsNode(), JsStatement {
+class JsSingleLineComment(text: String) : JsComment(text) {
override fun accept(visitor: JsVisitor) {
visitor.visitSingleLineComment(this)
}
- override fun acceptChildren(visitor: JsVisitor) {
- }
-
override fun traverse(visitor: JsVisitorWithContext, ctx: JsContext<*>) {
visitor.visit(this, ctx)
visitor.endVisit(this, ctx)
}
-
- override fun deepCopy() = this
}
\ No newline at end of file
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitor.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitor.kt
index 8561755..8dfd510 100644
--- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitor.kt
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitor.kt
@@ -39,6 +39,9 @@
open fun visitBlock(x: JsBlock): Unit =
visitElement(x)
+ open fun visitScript(x: JsScript): Unit =
+ visitElement(x)
+
open fun visitBoolean(x: JsBooleanLiteral): Unit =
visitElement(x)
@@ -165,6 +168,9 @@
open fun visitSingleLineComment(comment: JsSingleLineComment): Unit =
visitElement(comment)
+ open fun visitMultiLineComment(comment: JsMultiLineComment): Unit =
+ visitElement(comment)
+
open fun visitExport(export: JsExport): Unit =
visitElement(export)
diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContext.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContext.java
index 955c38e..106fa25 100644
--- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContext.java
+++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsVisitorWithContext.java
@@ -77,6 +77,9 @@
public void endVisit(@NotNull JsBlock x, @NotNull JsContext ctx) {
}
+ public void endVisit(@NotNull JsScript x, @NotNull JsContext ctx) {
+ }
+
public void endVisit(@NotNull JsBooleanLiteral x, @NotNull JsContext ctx) {
endVisit((JsExpression) x, ctx);
}
@@ -214,6 +217,9 @@
public void endVisit(@NotNull JsSingleLineComment x, @NotNull JsContext ctx) {
}
+ public void endVisit(@NotNull JsMultiLineComment x, @NotNull JsContext ctx) {
+ }
+
public void endVisit(@NotNull JsExport x, @NotNull JsContext ctx) {
}
@@ -240,6 +246,10 @@
return true;
}
+ public boolean visit(@NotNull JsScript x, @NotNull JsContext ctx) {
+ return true;
+ }
+
public boolean visit(@NotNull JsBooleanLiteral x, @NotNull JsContext ctx) {
return true;
}
@@ -404,6 +414,10 @@
return true;
}
+ public boolean visit(@NotNull JsMultiLineComment x, @NotNull JsContext ctx) {
+ return true;
+ }
+
public boolean visit(@NotNull JsExport x, @NotNull JsContext ctx) {
return true;
}
diff --git a/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java b/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java
index fac1ca8..5c7b55f 100644
--- a/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java
+++ b/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java
@@ -16,15 +16,14 @@
package com.google.gwt.dev.js;
import com.google.gwt.dev.js.parserExceptions.JsParserException;
-import com.google.gwt.dev.js.rhino.CodePosition;
-import com.google.gwt.dev.js.rhino.Node;
-import com.google.gwt.dev.js.rhino.TokenStream;
+import com.google.gwt.dev.js.rhino.*;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.js.backend.ast.*;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
public class JsAstMapper {
@@ -50,9 +49,7 @@
private JsNode mapWithoutLocation(Node node) throws JsParserException {
switch (node.getType()) {
case TokenStream.SCRIPT: {
- JsBlock block = new JsBlock();
- mapStatements(block.getStatements(), node);
- return block;
+ return mapScriptStatement(node);
}
case TokenStream.DEBUGGER:
@@ -892,6 +889,12 @@
return JsEmpty.INSTANCE;
}
}
+ public JsScript mapScriptStatement(Node node) {
+ List<JsComment> comments = mapComments(((Node.ScriptNode) node).getFirstComment());
+ JsScript result = new JsScript(comments);
+ mapStatements(result.getStatements(), node);
+ return result;
+ }
private void mapStatements(List<JsStatement> stmts, Node nodeStmts)
throws JsParserException {
@@ -1107,7 +1110,31 @@
withNode);
}
- private <T extends JsNode> T withLocation(T astNode, Node node) {
+ private List<JsComment> mapComments(Comment comment) {
+ List<JsComment> comments = new LinkedList<>();
+
+ while (comment != null) {
+ comments.add(mapComment(comment));
+ comment = comment.getNext();
+ }
+
+ return comments;
+ }
+
+ private JsComment mapComment(Comment comment) {
+ JsComment jsComment = comment.isMultiLine() ? mapMultiLineComment(comment) : mapSingleLineComment(comment);
+ return withLocation(jsComment, comment);
+ }
+
+ private JsSingleLineComment mapSingleLineComment(Comment comment) {
+ return new JsSingleLineComment(comment.getContent());
+ }
+
+ private JsMultiLineComment mapMultiLineComment(Comment comment) {
+ return new JsMultiLineComment(comment.getContent());
+ }
+
+ public <T extends JsNode> T withLocation(T astNode, WithCodePosition node) {
if (astNode == null) return null;
CodePosition location = node.getPosition();
diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Comment.kt b/js/js.parser/src/com/google/gwt/dev/js/rhino/Comment.kt
new file mode 100644
index 0000000..63442dd
--- /dev/null
+++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Comment.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package com.google.gwt.dev.js.rhino
+
+class Comment(
+ val content: String,
+ override val position: CodePosition,
+ val isMultiLine: Boolean,
+) : WithCodePosition {
+ var next: Comment? = null
+}
\ No newline at end of file
diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/IRFactory.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/IRFactory.java
index 1ab1de0..3d72efa 100644
--- a/js/js.parser/src/com/google/gwt/dev/js/rhino/IRFactory.java
+++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/IRFactory.java
@@ -35,6 +35,8 @@
package com.google.gwt.dev.js.rhino;
+import jdk.nashorn.internal.parser.Token;
+
/**
* This class allows the creation of nodes, and follows the Factory pattern.
*
@@ -48,9 +50,8 @@
/**
* Script (for associating file/url names with toplevel scripts.)
*/
- public Node createScript(Node body)
- {
- Node result = new Node(TokenStream.SCRIPT);
+ public Node.ScriptNode createScript(Node body, Comment firstComment) {
+ Node.ScriptNode result = Node.newScript(firstComment);
Node children = body.getFirstChild();
if (children != null)
result.addChildrenToBack(children);
diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Node.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Node.java
index faeb0c6..296a5a6 100644
--- a/js/js.parser/src/com/google/gwt/dev/js/rhino/Node.java
+++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Node.java
@@ -42,7 +42,7 @@
* This class implements the root of the intermediate representation.
*/
-public class Node implements Cloneable {
+public class Node implements Cloneable, WithCodePosition {
private static class NumberNode extends Node {
NumberNode(int type, double number, CodePosition position) {
@@ -90,6 +90,17 @@
private String str;
}
+ public static class ScriptNode extends Node {
+ ScriptNode(Comment firstComment, CodePosition position) {
+ super(TokenStream.SCRIPT, position);
+ this.firstComment = firstComment;
+ }
+
+ private Comment firstComment;
+
+ public Comment getFirstComment() { return firstComment; }
+ }
+
public Node(int nodeType) {
type = nodeType;
}
@@ -187,6 +198,10 @@
return new StringNode(TokenStream.STRING, str, position);
}
+ public static ScriptNode newScript(Comment firstComment) {
+ return new ScriptNode(firstComment, null);
+ }
+
public static Node newString(int type, String str, CodePosition position) {
return new StringNode(type, str, position);
}
@@ -342,6 +357,7 @@
return operation;
}
+ @Override
public CodePosition getPosition() {
return position;
}
diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java
index 0cd62bb..af3d13c 100644
--- a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java
+++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java
@@ -37,6 +37,7 @@
package com.google.gwt.dev.js.rhino;
+import jdk.nashorn.internal.parser.Token;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@@ -137,7 +138,7 @@
return null;
}
- return nf.createScript(tempBlock);
+ return nf.createScript(tempBlock, ts.getFirstComment());
}
/*
@@ -735,6 +736,13 @@
return pn;
}
+ public Node scriptExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
+ Node expression = expr(ts, inForInit);
+ Node tempBlock = nf.createLeaf(TokenStream.BLOCK, ts.tokenPosition);
+ tempBlock.addChildToBack(expression);
+ return nf.createScript(tempBlock, ts.getFirstComment());
+ }
+
private Node assignExpr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
Node pn = condExpr(ts, inForInit);
diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/TokenStream.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/TokenStream.java
index 017f05b..b419260 100644
--- a/js/js.parser/src/com/google/gwt/dev/js/rhino/TokenStream.java
+++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/TokenStream.java
@@ -253,7 +253,7 @@
LAST_TOKEN = 147,
NUMBER_INT = 148,
-
+
// This value is only used as a return value for getTokenHelper,
// which is only called from getToken and exists to avoid an excessive
// recursion problem if a number of lines in a row are comments.
@@ -412,7 +412,7 @@
case JSR: return "jsr";
case NEWLOCAL: return "newlocal";
case USELOCAL: return "uselocal";
- case SCRIPT: return "script";
+ case SCRIPT: return "script";
}
return "<unknown="+token+">";
}
@@ -1059,19 +1059,29 @@
case '/':
// is it a // comment?
if (in.match('/')) {
- skipLine();
+ CodePosition commentPosition = tokenPosition;
+ stringBufferTop = 0;
+ while ((c = in.read()) != -1 && c != '\n') {
+ addToString(c);
+ }
+ this.addSingleLineComment(new String(stringBuffer, 0, stringBufferTop), commentPosition);
return RETRY_TOKEN;
}
if (in.match('*')) {
+ CodePosition commentPosition = tokenPosition;
+ stringBufferTop = 0;
while ((c = in.read()) != -1 &&
!(c == '*' && in.match('/'))) {
+ addToString(c);
; // empty loop body
}
if (c == EOF_CHAR) {
reportTokenError("msg.unterminated.comment", null);
return ERROR;
}
- return RETRY_TOKEN; // `goto retry'
+
+ this.addMultiLineComment(new String(stringBuffer, 0, stringBufferTop), commentPosition);
+ return RETRY_TOKEN;
}
// is it a regexp?
@@ -1454,7 +1464,26 @@
}
}
+ private void addComment(Comment comment) {
+ if (firstComment == null) {
+ firstComment = comment;
+ currentComment = comment;
+ } else {
+ currentComment.setNext(comment);
+ currentComment = comment;
+ }
+ }
+
+ private void addSingleLineComment(String content, CodePosition position) {
+ addComment(new Comment(content, position, false));
+ }
+
+ private void addMultiLineComment(String content, CodePosition position) {
+ addComment(new Comment(content, position, true));
+ }
+
public String getSourceName() { return sourceName; }
+ public Comment getFirstComment() { return firstComment; }
public int getLineno() { return in.getLineno(); }
public int getOp() { return op; }
public String getString() { return string; }
@@ -1496,4 +1525,7 @@
private char[] stringBuffer = new char[128];
private int stringBufferTop;
+
+ private Comment firstComment;
+ private Comment currentComment;
}
diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/WithCodePosition.kt b/js/js.parser/src/com/google/gwt/dev/js/rhino/WithCodePosition.kt
new file mode 100644
index 0000000..cc1708c
--- /dev/null
+++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/WithCodePosition.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package com.google.gwt.dev.js.rhino
+
+interface WithCodePosition {
+ val position: CodePosition
+}
\ No newline at end of file
diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt
index f76615c..69915d0 100644
--- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt
+++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt
@@ -38,7 +38,7 @@
val accumulatingReporter = AccumulatingReporter()
val exprNode = try {
parse(code, startPosition, 0, accumulatingReporter, true) {
- val result = expr(it, false)
+ val result = scriptExpr(it, false)
if (it.token != TokenStream.EOF) {
accumulatingReporter.hasErrors = true
}
@@ -61,7 +61,7 @@
else {
val node = parse(code, startPosition, 0, reporter, true, Parser::parse)
node?.toJsAst(scope, fileName) {
- mapStatements(it)
+ listOf(JsExpressionStatement(mapScriptStatement(it)))
}
}
}
@@ -71,7 +71,9 @@
addListener(FunctionParsingObserver())
primaryExpr(it)
}
- return rootNode?.toJsAst(scope, fileName, JsAstMapper::mapFunction)
+ return rootNode?.toJsAst(scope, fileName) {
+ mapFunction(it)
+ }
}
private class FunctionParsingObserver : ParserListener {
diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java
index ee99ae0..22ad11b3 100644
--- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java
+++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java
@@ -6109,6 +6109,12 @@
}
@Test
+ @TestMetadata("comments.kt")
+ public void testComments() throws Exception {
+ runTest("js/js.translator/testData/box/jsCode/comments.kt");
+ }
+
+ @Test
@TestMetadata("constantExpression.kt")
public void testConstantExpression() throws Exception {
runTest("js/js.translator/testData/box/jsCode/constantExpression.kt");
diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java
index 497afc6..15062e3 100644
--- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java
+++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java
@@ -6487,6 +6487,12 @@
}
@Test
+ @TestMetadata("comments.kt")
+ public void testComments() throws Exception {
+ runTest("js/js.translator/testData/box/jsCode/comments.kt");
+ }
+
+ @Test
@TestMetadata("constantExpression.kt")
public void testConstantExpression() throws Exception {
runTest("js/js.translator/testData/box/jsCode/constantExpression.kt");
diff --git a/js/js.translator/testData/box/inline/jsCode.kt b/js/js.translator/testData/box/inline/jsCode.kt
index 3934fab..d0459bb 100644
--- a/js/js.translator/testData/box/inline/jsCode.kt
+++ b/js/js.translator/testData/box/inline/jsCode.kt
@@ -5,7 +5,7 @@
// CHECK_CONTAINS_NO_CALLS: test TARGET_BACKENDS=JS
// CHECK_NOT_CALLED_IN_SCOPE: function=sum scope=test
-internal inline fun sum(x: Int, y: Int): Int = js("x + y")
+fun sum(x: Int, y: Int): Int = js("/*before x*/ x /*before +*/ + /*after +*/ y /*after y*/")
internal fun test(x: Int, y: Int): Int = sum(sum(x, x), sum(y, y))
diff --git a/js/js.translator/testData/box/jsCode/comments.kt b/js/js.translator/testData/box/jsCode/comments.kt
new file mode 100644
index 0000000..361dd30
--- /dev/null
+++ b/js/js.translator/testData/box/jsCode/comments.kt
@@ -0,0 +1,33 @@
+// EXPECTED_REACHABLE_NODES: 1282
+package foo
+
+fun simpleSingleLineComment() {
+ js("""
+ // This is 'simpleSingleLineComment' comment
+ console.log('simpleSingleLineComment');
+ """)
+}
+
+fun simpleMultilineComment() {
+ js("""
+ /*
+ This is 'simpleMultilineComment' comment
+ */
+ console.log('simpleSingleLineComment');
+ """)
+}
+
+fun complexMultilineComment() {
+ js("""
+ function mul(a/*: float*/, b/*: float*/)/*: int*/ {
+ return /*int(*/a/*)*/ * /*int(*/b/*)*/;
+ }
+ """)
+}
+
+fun box(): String {
+ simpleSingleLineComment()
+ simpleMultilineComment()
+ complexMultilineComment()
+ return "OK"
+}
\ No newline at end of file