diff --git a/.idea/artifacts/KotlinPlugin.xml b/.idea/artifacts/KotlinPlugin.xml
index db1665a520c..b5adfe25356 100644
--- a/.idea/artifacts/KotlinPlugin.xml
+++ b/.idea/artifacts/KotlinPlugin.xml
@@ -21,6 +21,7 @@
+
diff --git a/js/js.frontend/js.frontend.iml b/js/js.frontend/js.frontend.iml
index 887bd1954ef..0d9bc35f0f2 100644
--- a/js/js.frontend/js.frontend.iml
+++ b/js/js.frontend/js.frontend.iml
@@ -10,6 +10,7 @@
+
\ No newline at end of file
diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/KotlinJsCheckerProvider.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/KotlinJsCheckerProvider.kt
index 75f7415a78d..4d0e835344b 100644
--- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/KotlinJsCheckerProvider.kt
+++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/KotlinJsCheckerProvider.kt
@@ -31,11 +31,11 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
-import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
+import org.jetbrains.kotlin.resolve.diagnostics.JsCallChecker
public object KotlinJsCheckerProvider : AdditionalCheckerProvider(
annotationCheckers = listOf(NativeInvokeChecker(), NativeGetterChecker(), NativeSetterChecker()),
- additionalCallCheckers = listOf(),
+ additionalCallCheckers = listOf(JsCallChecker()),
additionalTypeCheckers = listOf()
)
diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java
index f587eade68e..fb780d8ee09 100644
--- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java
+++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java
@@ -17,13 +17,11 @@
package org.jetbrains.kotlin.js.resolve.diagnostics;
import com.intellij.openapi.util.TextRange;
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2;
-import org.jetbrains.kotlin.diagnostics.Errors;
+import org.jetbrains.kotlin.diagnostics.*;
import org.jetbrains.kotlin.psi.JetDeclaration;
import org.jetbrains.kotlin.psi.JetElement;
import org.jetbrains.kotlin.psi.JetExpression;
+import org.jetbrains.kotlin.resolve.diagnostics.JsCallData;
import org.jetbrains.kotlin.types.JetType;
import java.util.List;
@@ -39,9 +37,9 @@ public interface ErrorsJs {
DiagnosticFactory0 NATIVE_GETTER_RETURN_TYPE_SHOULD_BE_NULLABLE = DiagnosticFactory0.create(ERROR, DECLARATION_RETURN_TYPE);
DiagnosticFactory0 NATIVE_SETTER_WRONG_RETURN_TYPE = DiagnosticFactory0.create(ERROR, DECLARATION_RETURN_TYPE);
DiagnosticFactory2 NATIVE_INDEXER_WRONG_PARAMETER_COUNT = DiagnosticFactory2.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
- DiagnosticFactory2> JSCODE_ERROR = DiagnosticFactory2.create(ERROR, JsCodePositioningStrategy.INSTANCE$);
- DiagnosticFactory2> JSCODE_WARNING = DiagnosticFactory2.create(WARNING, JsCodePositioningStrategy.INSTANCE$);
- DiagnosticFactory0 JSCODE_ARGUMENT_SHOULD_BE_LITERAL = DiagnosticFactory0.create(ERROR, CALL_EXPRESSION);
+ DiagnosticFactory1 JSCODE_ERROR = DiagnosticFactory1.create(ERROR, JsCodePositioningStrategy.INSTANCE$);
+ DiagnosticFactory1 JSCODE_WARNING = DiagnosticFactory1.create(WARNING, JsCodePositioningStrategy.INSTANCE$);
+ DiagnosticFactory0 JSCODE_ARGUMENT_SHOULD_BE_CONSTANT = DiagnosticFactory0.create(ERROR, DEFAULT);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCallChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCallChecker.kt
new file mode 100644
index 00000000000..07a4264aa0e
--- /dev/null
+++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCallChecker.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2010-2015 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.resolve.diagnostics
+
+import com.google.gwt.dev.js.AbortParsingException
+import com.google.gwt.dev.js.rhino.*
+import com.google.gwt.dev.js.rhino.Utils.*
+import org.jetbrains.annotations.TestOnly
+import org.jetbrains.kotlin.builtins.KotlinBuiltIns
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
+import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
+import org.jetbrains.kotlin.diagnostics.DiagnosticSink
+import org.jetbrains.kotlin.diagnostics.ParametrizedDiagnostic
+import org.jetbrains.kotlin.js.patterns.DescriptorPredicate
+import org.jetbrains.kotlin.js.patterns.PatternBuilder
+import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs
+import org.jetbrains.kotlin.psi.JetCallExpression
+import org.jetbrains.kotlin.psi.JetExpression
+import org.jetbrains.kotlin.psi.JetLiteralStringTemplateEntry
+import org.jetbrains.kotlin.psi.JetStringTemplateExpression
+import org.jetbrains.kotlin.renderer.Renderer
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.BindingTrace
+import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
+import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
+import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
+import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
+import org.jetbrains.kotlin.types.JetType
+
+import com.intellij.openapi.util.TextRange
+import java.io.StringReader
+
+import kotlin.platform.platformStatic
+import org.jetbrains.kotlin.resolve.TemporaryBindingTrace
+
+public class JsCallChecker : CallChecker {
+
+ class object {
+ private val JS_PATTERN: DescriptorPredicate = PatternBuilder.pattern("kotlin.js.js(String)")
+
+ platformStatic
+ public fun ResolvedCall.isJsCall(): Boolean {
+ val descriptor = getResultingDescriptor()
+ return descriptor is SimpleFunctionDescriptor && JS_PATTERN.apply(descriptor)
+ }
+ }
+
+ override fun check(resolvedCall: ResolvedCall, context: BasicCallResolutionContext) {
+ if (context.isAnnotationContext || !resolvedCall.isJsCall()) return
+
+ val expression = resolvedCall.getCall().getCallElement()
+ if (expression !is JetCallExpression) return
+
+ val arguments = expression.getValueArgumentList()?.getArguments()
+ val argument = arguments?.firstOrNull()?.getArgumentExpression()
+
+ if (argument == null) return
+
+ val stringType = KotlinBuiltIns.getInstance().getStringType()
+ val trace = TemporaryBindingTrace.create(context.trace, "JsCallChecker")
+ val evaluationResult = ConstantExpressionEvaluator.evaluate(argument, trace, stringType)
+
+ if (evaluationResult == null) {
+ context.trace.report(ErrorsJs.JSCODE_ARGUMENT_SHOULD_BE_CONSTANT.on(argument))
+ return
+ }
+
+ val code = evaluationResult.getValue() as String
+ val reader = StringReader(code)
+ val errorReporter = JsCodeErrorReporter(argument, code, context.trace)
+ Context.enter().setErrorReporter(errorReporter)
+
+ try {
+ val ts = TokenStream(reader, "js", 0)
+ val parser = Parser(IRFactory(ts), /* insideFunction = */ true)
+ parser.parse(ts)
+ } catch (e: AbortParsingException) {
+ // ignore
+ } finally {
+ Context.exit()
+ }
+ }
+}
+
+class JsCodeErrorReporter(
+ private val nodeToReport: JetExpression,
+ private val code: String,
+ private val trace: BindingTrace
+) : ErrorReporter {
+ override fun warning(message: String, startPosition: CodePosition, endPosition: CodePosition) {
+ report(ErrorsJs.JSCODE_WARNING, message, startPosition, endPosition)
+ }
+
+ override fun error(message: String, startPosition: CodePosition, endPosition: CodePosition) {
+ report(ErrorsJs.JSCODE_ERROR, message, startPosition, endPosition)
+ throw AbortParsingException()
+ }
+
+ private fun report(
+ diagnosticFactory: DiagnosticFactory1,
+ message: String,
+ startPosition: CodePosition,
+ endPosition: CodePosition
+ ) {
+ val data = when {
+ nodeToReport.isConstantStringLiteral -> {
+ val reportRange = TextRange(startPosition.absoluteOffset, endPosition.absoluteOffset)
+ JsCallData(reportRange, message)
+ }
+ else -> {
+ val reportRange = nodeToReport.getTextRange()
+ val codeRange = TextRange(code.offsetOf(startPosition), code.offsetOf(endPosition))
+ JsCallDataWithCode(reportRange, message, code, codeRange)
+ }
+ }
+
+ val parametrizedDiagnostic = diagnosticFactory.on(nodeToReport, data)
+ trace.report(parametrizedDiagnostic)
+ }
+
+ private val CodePosition.absoluteOffset: Int
+ get() {
+ val quotesLength = nodeToReport.getFirstChild().getTextLength()
+ return nodeToReport.getTextOffset() + quotesLength + code.offsetOf(this)
+ }
+}
+
+/**
+ * Calculates an offset from the start of a text for a position,
+ * defined by line and offset in that line.
+ */
+private fun String.offsetOf(position: CodePosition): Int {
+ var i = 0
+ var lineCount = 0
+ var offsetInLine = 0
+
+ while (i < length()) {
+ val c = charAt(i)
+
+ if (lineCount == position.line && offsetInLine == position.offset) {
+ return i
+ }
+
+ i++
+ offsetInLine++
+
+ if (isEndOfLine(c.toInt())) {
+ offsetInLine = 0
+ lineCount++
+ assert(lineCount <= position.line)
+ }
+ }
+
+ return length()
+}
+
+private val JetExpression.isConstantStringLiteral: Boolean
+ get() = this is JetStringTemplateExpression && getEntries().all { it is JetLiteralStringTemplateEntry }
+
+open class JsCallData(val reportRange: TextRange, val message: String)
+
+class JsCallDataWithCode(
+ reportRange: TextRange,
+ message: String,
+ val code: String,
+ val codeRange: TextRange
+) : JsCallData(reportRange, message)
diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCodePositioningStrategy.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCodePositioningStrategy.kt
index 190de00b1cc..183d28b2264 100644
--- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCodePositioningStrategy.kt
+++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsCodePositioningStrategy.kt
@@ -20,13 +20,17 @@ import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.PositioningStrategy
import org.jetbrains.kotlin.psi.JetExpression
-import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters2
+import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters3
import org.jetbrains.kotlin.diagnostics.ParametrizedDiagnostic
+import org.jetbrains.kotlin.resolve.diagnostics.JsCallData
+import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
+import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1
public object JsCodePositioningStrategy : PositioningStrategy() {
override fun markDiagnostic(diagnostic: ParametrizedDiagnostic): List {
[suppress("UNCHECKED_CAST")]
- val diagnosticWithParameters = diagnostic as DiagnosticWithParameters2>
- return diagnosticWithParameters.getB()
+ val diagnosticWithParameters = diagnostic as DiagnosticWithParameters1
+ val textRange = diagnosticWithParameters.getA().reportRange
+ return listOf(textRange)
}
}
diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java
index 8ee67a82a0e..d2ef6beb845 100644
--- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java
+++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java
@@ -22,18 +22,13 @@ import com.google.dart.compiler.common.SourceInfoImpl;
import com.google.gwt.dev.js.AbortParsingException;
import com.google.gwt.dev.js.JsParser;
import com.google.gwt.dev.js.JsParserException;
+import com.google.gwt.dev.js.rhino.CodePosition;
import com.google.gwt.dev.js.rhino.ErrorReporter;
-import com.google.gwt.dev.js.rhino.EvaluatorException;
-import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.InlineStrategy;
import org.jetbrains.kotlin.builtins.InlineUtil;
import org.jetbrains.kotlin.descriptors.*;
-import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2;
-import org.jetbrains.kotlin.diagnostics.DiagnosticSink;
-import org.jetbrains.kotlin.diagnostics.ParametrizedDiagnostic;
-import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs;
import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.psi.JetCallExpression;
@@ -50,10 +45,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import static com.google.gwt.dev.js.rhino.Utils.isEndOfLine;
-import static org.jetbrains.kotlin.js.descriptors.DescriptorsPackage.getJS_PATTERN;
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCompileTimeValue;
import static org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilPackage.getFunctionResolvedCallWithAssert;
+import static org.jetbrains.kotlin.resolve.diagnostics.JsCallChecker.isJsCall;
public final class CallExpressionTranslator extends AbstractCallExpressionTranslator {
@@ -63,7 +57,9 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
@Nullable JsExpression receiver,
@NotNull TranslationContext context
) {
- if (matchesJsCode(expression, context)) {
+ ResolvedCall extends FunctionDescriptor> resolvedCall = getFunctionResolvedCallWithAssert(expression, context.bindingContext());
+
+ if (isJsCall(resolvedCall)) {
return (new CallExpressionTranslator(expression, receiver, context)).translateJsCode();
}
@@ -105,16 +101,6 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
return false;
}
- private static boolean matchesJsCode(
- @NotNull JetCallExpression expression,
- @NotNull TranslationContext context
- ) {
- FunctionDescriptor descriptor = getFunctionResolvedCallWithAssert(expression, context.bindingContext())
- .getResultingDescriptor();
-
- return getJS_PATTERN().apply(descriptor);
- }
-
private CallExpressionTranslator(
@NotNull JetCallExpression expression,
@Nullable JsExpression receiver,
@@ -132,11 +118,7 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
private JsNode translateJsCode() {
List extends ValueArgument> arguments = expression.getValueArguments();
JetExpression argumentExpression = arguments.get(0).getArgumentExpression();
-
- if (!(argumentExpression instanceof JetStringTemplateExpression)) {
- context().getTrace().report(ErrorsJs.JSCODE_ARGUMENT_SHOULD_BE_LITERAL.on(expression));
- return program().getEmptyExpression();
- }
+ assert argumentExpression instanceof JetStringTemplateExpression;
List statements = parseJsCode((JetStringTemplateExpression) argumentExpression);
int size = statements.size();
@@ -161,7 +143,21 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
assert jsCode instanceof String: "jsCode must be compile time string";
List statements = new ArrayList();
- ErrorReporter errorReporter = new JsCodeErrorReporter(jsCodeExpression, (String) jsCode, context().getTrace());
+ ErrorReporter errorReporter = new ErrorReporter() {
+ @Override
+ public void warning(
+ @NotNull String message, @NotNull CodePosition startPosition, @NotNull CodePosition endPosition
+ ) {
+
+ }
+
+ @Override
+ public void error(
+ @NotNull String message, @NotNull CodePosition startPosition, @NotNull CodePosition endPosition
+ ) {
+ throw new IllegalStateException("JS parser error in backend (must have been checked in frontend): " + message);
+ }
+ };
try {
SourceInfoImpl info = new SourceInfoImpl(null, 0, 0, 0, 0);
@@ -179,83 +175,4 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
return statements;
}
-
- private static class JsCodeErrorReporter implements ErrorReporter {
-
- @NotNull
- private final JetExpression jsCodeExpression;
-
- @NotNull
- private final String code;
-
- @NotNull
- private final DiagnosticSink trace;
-
- JsCodeErrorReporter(@NotNull JetStringTemplateExpression jsCodeExpression,
- @NotNull String code,
- @NotNull DiagnosticSink trace
- ) {
- this.jsCodeExpression = jsCodeExpression;
- this.code = code;
- this.trace = trace;
- }
-
- @Override
- public void error(String message, String sourceName, int line, String lineSource, int lineOffset) {
- ParametrizedDiagnostic diagnostic = getDiagnostic(ErrorsJs.JSCODE_ERROR, message, line, lineOffset);
- trace.report(diagnostic);
- throw new AbortParsingException();
- }
-
- @Override
- public void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {
- ParametrizedDiagnostic diagnostic = getDiagnostic(ErrorsJs.JSCODE_WARNING, message, line, lineOffset);
- trace.report(diagnostic);
- }
-
- private ParametrizedDiagnostic getDiagnostic(
- @NotNull DiagnosticFactory2> diagnosticFactory,
- String message,
- int line,
- int lineOffset
- ) {
- int offset = jsCodeExpression.getTextOffset() + offsetFromStart(code, line, lineOffset);
-
- assert jsCodeExpression instanceof JetStringTemplateExpression: "js argument is expected to be compile-time string literal";
- int quotesLength = jsCodeExpression.getFirstChild().getTextLength();
- offset += quotesLength;
-
- TextRange textRange = new TextRange(offset, offset + 1);
- return diagnosticFactory.on(jsCodeExpression, message, Collections.singletonList(textRange));
- }
-
- /**
- * Calculates an offset from the start of a text for a position,
- * defined by line and offset in that line.
- */
- private static int offsetFromStart(String text, int line, int offset) {
- int i = 0;
- int lineCount = 0;
- int offsetInLine = 0;
-
- while (i < text.length()) {
- char c = text.charAt(i);
-
- if (lineCount == line && offsetInLine == offset) {
- return i;
- }
-
- if (isEndOfLine(c)) {
- offsetInLine = 0;
- lineCount++;
- assert lineCount <= line;
- }
-
- i++;
- offsetInLine++;
- }
-
- return text.length();
- }
- }
}