JS: check js() call at frontend
This commit is contained in:
Generated
+1
@@ -21,6 +21,7 @@
|
||||
<element id="module-output" name="util.runtime" />
|
||||
<element id="module-output" name="j2k" />
|
||||
<element id="module-output" name="js.frontend" />
|
||||
<element id="module-output" name="js.parser" />
|
||||
<element id="module-output" name="cli-common" />
|
||||
<element id="module-output" name="idea-jps-common" />
|
||||
<element id="module-output" name="preloader" />
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<orderEntry type="library" scope="PROVIDED" name="intellij-core" level="project" />
|
||||
<orderEntry type="module" module-name="frontend" />
|
||||
<orderEntry type="module" module-name="js.dart-ast" exported="" />
|
||||
<orderEntry type="module" module-name="js.parser" exported="" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
|
||||
@@ -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<JetDeclaration> NATIVE_GETTER_RETURN_TYPE_SHOULD_BE_NULLABLE = DiagnosticFactory0.create(ERROR, DECLARATION_RETURN_TYPE);
|
||||
DiagnosticFactory0<JetDeclaration> NATIVE_SETTER_WRONG_RETURN_TYPE = DiagnosticFactory0.create(ERROR, DECLARATION_RETURN_TYPE);
|
||||
DiagnosticFactory2<JetElement, Integer, String> NATIVE_INDEXER_WRONG_PARAMETER_COUNT = DiagnosticFactory2.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
|
||||
DiagnosticFactory2<JetExpression, String, List<TextRange>> JSCODE_ERROR = DiagnosticFactory2.create(ERROR, JsCodePositioningStrategy.INSTANCE$);
|
||||
DiagnosticFactory2<JetExpression, String, List<TextRange>> JSCODE_WARNING = DiagnosticFactory2.create(WARNING, JsCodePositioningStrategy.INSTANCE$);
|
||||
DiagnosticFactory0<JetExpression> JSCODE_ARGUMENT_SHOULD_BE_LITERAL = DiagnosticFactory0.create(ERROR, CALL_EXPRESSION);
|
||||
DiagnosticFactory1<JetExpression, JsCallData> JSCODE_ERROR = DiagnosticFactory1.create(ERROR, JsCodePositioningStrategy.INSTANCE$);
|
||||
DiagnosticFactory1<JetExpression, JsCallData> JSCODE_WARNING = DiagnosticFactory1.create(WARNING, JsCodePositioningStrategy.INSTANCE$);
|
||||
DiagnosticFactory0<JetExpression> JSCODE_ARGUMENT_SHOULD_BE_CONSTANT = DiagnosticFactory0.create(ERROR, DEFAULT);
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
Object _initializer = new Object() {
|
||||
|
||||
@@ -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 <F : CallableDescriptor?> ResolvedCall<F>.isJsCall(): Boolean {
|
||||
val descriptor = getResultingDescriptor()
|
||||
return descriptor is SimpleFunctionDescriptor && JS_PATTERN.apply(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <F : CallableDescriptor?> check(resolvedCall: ResolvedCall<F>, 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<JetExpression, JsCallData>,
|
||||
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)
|
||||
+7
-3
@@ -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<PsiElement>() {
|
||||
override fun markDiagnostic(diagnostic: ParametrizedDiagnostic<out PsiElement>): List<TextRange> {
|
||||
[suppress("UNCHECKED_CAST")]
|
||||
val diagnosticWithParameters = diagnostic as DiagnosticWithParameters2<JetExpression, String, List<TextRange>>
|
||||
return diagnosticWithParameters.getB()
|
||||
val diagnosticWithParameters = diagnostic as DiagnosticWithParameters1<JetExpression, JsCallData>
|
||||
val textRange = diagnosticWithParameters.getA().reportRange
|
||||
return listOf(textRange)
|
||||
}
|
||||
}
|
||||
|
||||
+21
-104
@@ -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<JsStatement> 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<JsStatement> statements = new ArrayList<JsStatement>();
|
||||
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<JetExpression> 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<JetExpression> diagnostic = getDiagnostic(ErrorsJs.JSCODE_WARNING, message, line, lineOffset);
|
||||
trace.report(diagnostic);
|
||||
}
|
||||
|
||||
private ParametrizedDiagnostic<JetExpression> getDiagnostic(
|
||||
@NotNull DiagnosticFactory2<JetExpression, String, List<TextRange>> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user