JS: fix parsing of object literals in js() function.

Also, fix generation of source maps of content of js() function.

See KT-19794
This commit is contained in:
Alexey Andreev
2017-09-29 13:09:42 +03:00
parent c15847a957
commit 27e319a279
15 changed files with 183 additions and 31 deletions
@@ -29,7 +29,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
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.parser.parse
import org.jetbrains.kotlin.js.parser.parseExpressionOrStatement
import org.jetbrains.kotlin.js.patterns.DescriptorPredicate
import org.jetbrains.kotlin.js.patterns.PatternBuilder
import org.jetbrains.kotlin.js.resolve.LEXICAL_SCOPE_FOR_JS
@@ -90,7 +90,8 @@ class JsCallChecker(
try {
val parserScope = JsFunctionScope(JsRootScope(JsProgram()), "<js fun>")
val statements = parse(code, errorReporter, parserScope, reportOn.containingFile?.name ?: "<unknown file>")
val statements = parseExpressionOrStatement(
code, errorReporter, parserScope, CodePosition(0, 0), reportOn.containingFile?.name ?: "<unknown file>")
if (statements == null || statements.isEmpty()) {
context.trace.report(ErrorsJs.JSCODE_NO_JAVASCRIPT_PRODUCED.on(argument))
@@ -462,7 +462,7 @@ public class JsAstMapper {
}
}
private JsExpression mapExpression(Node exprNode) throws JsParserException {
public JsExpression mapExpression(Node exprNode) throws JsParserException {
JsNode unknown = map(exprNode);
if (unknown instanceof JsExpression) {
@@ -727,7 +727,7 @@ public class Parser {
return pn;
}
private Node expr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
public Node expr(TokenStream ts, boolean inForInit) throws IOException, JavaScriptException {
Node pn = assignExpr(ts, inForInit);
while (ts.matchToken(TokenStream.COMMA)) {
CodePosition position = ts.tokenPosition;
@@ -30,6 +30,42 @@ fun parse(code: String, reporter: ErrorReporter, scope: JsScope, fileName: Strin
}
}
fun parseExpressionOrStatement(
code: String,
reporter: ErrorReporter, scope: JsScope,
startPosition: CodePosition, fileName: String
): List<JsStatement>? {
val accumulatingReporter = AccumulatingReporter()
val exprNode = try {
parse(code, startPosition, 0, accumulatingReporter, true) {
val result = expr(it, false)
if (it.token != TokenStream.EOF) {
accumulatingReporter.hasErrors = true
}
result
}
}
catch (e: JavaScriptException) {
null
}
return if (!accumulatingReporter.hasErrors) {
for (warning in accumulatingReporter.warnings) {
reporter.warning(warning.message, warning.startPosition, warning.endPosition)
}
val expr = exprNode?.toJsAst(scope, fileName) {
mapExpression(it)
}
expr?.let { listOf(JsExpressionStatement(it)) }
}
else {
val node = parse(code, startPosition, 0, reporter, true, Parser::parse)
node?.toJsAst(scope, fileName) {
mapStatements(it)
}
}
}
fun parseFunction(code: String, fileName: String, position: CodePosition, offset: Int, reporter: ErrorReporter, scope: JsScope): JsFunction? {
val rootNode = parse(code, position, offset, reporter, insideFunction = false) {
addListener(FunctionParsingObserver())
@@ -80,4 +116,20 @@ private fun StringReader(string: String, offset: Int): Reader {
val reader = StringReader(string)
reader.skip(offset.toLong())
return reader
}
}
private class AccumulatingReporter : ErrorReporter {
var hasErrors = false
val warnings = mutableListOf<Warning>()
override fun warning(message: String, startPosition: CodePosition, endPosition: CodePosition) {
warnings += Warning(message, startPosition, endPosition)
}
override fun error(message: String, startPosition: CodePosition, endPosition: CodePosition) {
hasErrors = true
}
class Warning(val message: String, val startPosition: CodePosition, val endPosition: CodePosition)
}
@@ -216,6 +216,12 @@ public class JsLineNumberTestGenerated extends AbstractJsLineNumberTest {
doTest(fileName);
}
@TestMetadata("jsCode.kt")
public void testJsCode() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/lineNumbers/jsCode.kt");
doTest(fileName);
}
@TestMetadata("lambdaWithClosure.kt")
public void testLambdaWithClosure() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/lineNumbers/lambdaWithClosure.kt");
@@ -5516,6 +5516,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName);
}
@TestMetadata("objectExpression.kt")
public void testObjectExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsCode/objectExpression.kt");
doTest(fileName);
}
@TestMetadata("objectScopes.kt")
public void testObjectScopes() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/jsCode/objectScopes.kt");
@@ -128,7 +128,11 @@ public final class K2JSTranslator {
ModuleDescriptor moduleDescriptor = analysisResult.getModuleDescriptor();
Diagnostics diagnostics = bindingTrace.getBindingContext().getDiagnostics();
AstGenerationResult translationResult = Translation.generateAst(bindingTrace, units, mainCallParameters, moduleDescriptor, config);
List<File> sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList());
SourceFilePathResolver pathResolver = new SourceFilePathResolver(sourceRoots);
AstGenerationResult translationResult = Translation.generateAst(
bindingTrace, units, mainCallParameters, moduleDescriptor, config, pathResolver);
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
@@ -152,9 +156,6 @@ public final class K2JSTranslator {
ExpandIsCallsKt.expandIsCalls(newFragments);
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
List<File> sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList());
SourceFilePathResolver pathResolver = new SourceFilePathResolver(sourceRoots);
JsAstSerializer serializer = new JsAstSerializer(file -> {
try {
return pathResolver.getPathRelativeToSourceRoots(file);
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.js.facade;
import com.intellij.openapi.editor.Document;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
@@ -26,6 +25,7 @@ import org.jetbrains.kotlin.js.backend.ast.JsLocation;
import org.jetbrains.kotlin.js.backend.ast.JsLocationWithSource;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.sourceMap.SourceMapMappingConsumer;
import org.jetbrains.kotlin.js.translate.utils.PsiUtils;
import java.io.*;
import java.nio.charset.Charset;
@@ -82,15 +82,10 @@ public class SourceMapBuilderConsumer implements SourceLocationConsumer {
}
if (sourceInfo instanceof PsiElement) {
PsiElement element = (PsiElement) sourceInfo;
PsiFile psiFile = element.getContainingFile();
int offset = element.getNode().getStartOffset();
Document document = psiFile.getViewProvider().getDocument();
assert document != null;
int sourceLine = document.getLineNumber(offset);
int sourceColumn = offset - document.getLineStartOffset(sourceLine);
File file = new File(psiFile.getViewProvider().getVirtualFile().getPath());
try {
JsLocation location = PsiUtils.extractLocationFromPsi(element, pathResolver);
PsiFile psiFile = element.getContainingFile();
File file = new File(psiFile.getViewProvider().getVirtualFile().getPath());
Supplier<Reader> contentSupplier;
if (provideCurrentModuleContent) {
contentSupplier = () -> {
@@ -105,8 +100,7 @@ public class SourceMapBuilderConsumer implements SourceLocationConsumer {
else {
contentSupplier = () -> null;
}
mappingConsumer.addMapping(pathResolver.getPathRelativeToSourceRoots(file), null, contentSupplier,
sourceLine, sourceColumn);
mappingConsumer.addMapping(location.getFile(), null, contentSupplier, location.getStartLine(), location.getStartChar());
}
catch (IOException e) {
throw new RuntimeException("IO error occurred generating source maps", e);
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.js.config.JsConfig;
import org.jetbrains.kotlin.js.naming.NameSuggestion;
import org.jetbrains.kotlin.js.naming.NameSuggestionKt;
import org.jetbrains.kotlin.js.naming.SuggestedName;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.translate.context.generator.Generator;
import org.jetbrains.kotlin.js.translate.context.generator.Rule;
import org.jetbrains.kotlin.js.translate.declaration.ClassModelGenerator;
@@ -137,10 +138,14 @@ public final class StaticContext {
private final Map<String, JsName> intrinsicNames = new HashMap<>();
@NotNull
private final SourceFilePathResolver sourceFilePathResolver;
public StaticContext(
@NotNull BindingTrace bindingTrace,
@NotNull JsConfig config,
@NotNull ModuleDescriptor moduleDescriptor
@NotNull ModuleDescriptor moduleDescriptor,
@NotNull SourceFilePathResolver sourceFilePathResolver
) {
program = new JsProgram();
JsFunction rootFunction = JsAstUtils.createFunctionWithEmptyBody(program.getScope());
@@ -157,6 +162,7 @@ public final class StaticContext {
createImportedModule(new JsImportedModuleKey(Namer.KOTLIN_LOWER_NAME, null), Namer.KOTLIN_LOWER_NAME, kotlinName, null);
classModelGenerator = new ClassModelGenerator(TranslationContext.rootContext(this));
this.sourceFilePathResolver = sourceFilePathResolver;
}
@NotNull
@@ -189,6 +195,11 @@ public final class StaticContext {
return namer;
}
@NotNull
public SourceFilePathResolver getSourceFilePathResolver() {
return sourceFilePathResolver;
}
@NotNull
public JsScope getScopeForDescriptor(@NotNull DeclarationDescriptor descriptor) {
if (descriptor instanceof ModuleDescriptor) {
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.SpecialFunction;
import org.jetbrains.kotlin.js.config.JsConfig;
import org.jetbrains.kotlin.js.naming.NameSuggestion;
import org.jetbrains.kotlin.js.naming.SuggestedName;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.translate.declaration.ClassModelGenerator;
import org.jetbrains.kotlin.js.translate.intrinsic.Intrinsics;
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator;
@@ -896,4 +897,9 @@ public class TranslationContext {
});
}
}
@NotNull
public SourceFilePathResolver getSourceFilePathResolver() {
return staticContext.getSourceFilePathResolver();
}
}
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.js.facade.TranslationUnit;
import org.jetbrains.kotlin.js.facade.exceptions.TranslationException;
import org.jetbrains.kotlin.js.facade.exceptions.TranslationRuntimeException;
import org.jetbrains.kotlin.js.facade.exceptions.UnsupportedFeatureException;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
import org.jetbrains.kotlin.js.translate.context.Namer;
import org.jetbrains.kotlin.js.translate.context.StaticContext;
@@ -253,10 +254,11 @@ public final class Translation {
@NotNull Collection<TranslationUnit> units,
@NotNull MainCallParameters mainCallParameters,
@NotNull ModuleDescriptor moduleDescriptor,
@NotNull JsConfig config
@NotNull JsConfig config,
@NotNull SourceFilePathResolver sourceFilePathResolver
) throws TranslationException {
try {
return doGenerateAst(bindingTrace, units, mainCallParameters, moduleDescriptor, config);
return doGenerateAst(bindingTrace, units, mainCallParameters, moduleDescriptor, config, sourceFilePathResolver);
}
catch (UnsupportedOperationException e) {
throw new UnsupportedFeatureException("Unsupported feature used.", e);
@@ -272,7 +274,8 @@ public final class Translation {
@NotNull Collection<TranslationUnit> units,
@NotNull MainCallParameters mainCallParameters,
@NotNull ModuleDescriptor moduleDescriptor,
@NotNull JsConfig config
@NotNull JsConfig config,
@NotNull SourceFilePathResolver sourceFilePathResolver
) {
JsProgram program = new JsProgram();
JsFunction rootFunction = new JsFunction(program.getRootScope(), new JsBlock(), "root function");
@@ -290,7 +293,7 @@ public final class Translation {
for (TranslationUnit unit : units) {
if (unit instanceof TranslationUnit.SourceFile) {
KtFile file = ((TranslationUnit.SourceFile) unit).getFile();
StaticContext staticContext = new StaticContext(bindingTrace, config, moduleDescriptor);
StaticContext staticContext = new StaticContext(bindingTrace, config, moduleDescriptor, sourceFilePathResolver);
TranslationContext context = TranslationContext.rootContext(staticContext);
List<DeclarationDescriptor> fileMemberScope = new ArrayList<>();
translateFile(context, file, fileMemberScope);
@@ -308,7 +311,7 @@ public final class Translation {
}
}
JsProgramFragment testFragment = mayBeGenerateTests(config, bindingTrace, moduleDescriptor);
JsProgramFragment testFragment = mayBeGenerateTests(config, bindingTrace, moduleDescriptor, sourceFilePathResolver);
fragments.add(testFragment);
newFragments.add(testFragment);
merger.addFragment(testFragment);
@@ -316,7 +319,7 @@ public final class Translation {
if (mainCallParameters.shouldBeGenerated()) {
JsProgramFragment mainCallFragment = generateCallToMain(
bindingTrace, config, moduleDescriptor, mainCallParameters.arguments());
bindingTrace, config, moduleDescriptor, sourceFilePathResolver, mainCallParameters.arguments());
if (mainCallFragment != null) {
fragments.add(mainCallFragment);
newFragments.add(mainCallFragment);
@@ -400,9 +403,9 @@ public final class Translation {
@NotNull
private static JsProgramFragment mayBeGenerateTests(
@NotNull JsConfig config, @NotNull BindingTrace trace,
@NotNull ModuleDescriptor moduleDescriptor
@NotNull ModuleDescriptor moduleDescriptor, @NotNull SourceFilePathResolver sourceFilePathResolver
) {
StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor);
StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor, sourceFilePathResolver);
TranslationContext context = TranslationContext.rootContext(staticContext);
new JSTestGenerator(context).generateTestCalls(moduleDescriptor);
@@ -414,9 +417,10 @@ public final class Translation {
@Nullable
private static JsProgramFragment generateCallToMain(
@NotNull BindingTrace trace, @NotNull JsConfig config, @NotNull ModuleDescriptor moduleDescriptor,
@NotNull SourceFilePathResolver sourceFilePathResolver,
@NotNull List<String> arguments
) {
StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor);
StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor, sourceFilePathResolver);
TranslationContext context = TranslationContext.rootContext(staticContext);
MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(context.bindingContext());
FunctionDescriptor functionDescriptor = mainFunctionDetector.getMainFunction(moduleDescriptor);
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.js.translate.reference;
import com.google.gwt.dev.js.ThrowExceptionOnErrorReporter;
import com.google.gwt.dev.js.rhino.CodePosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.config.CommonConfigurationKeys;
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.js.resolve.BindingContextSlicesJsKt;
import org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker;
import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.utils.PsiUtils;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtCallExpression;
import org.jetbrains.kotlin.psi.KtExpression;
@@ -40,6 +42,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
import java.io.IOException;
import java.util.*;
import static org.jetbrains.kotlin.js.resolve.diagnostics.JsCallChecker.isJsCall;
@@ -175,6 +178,18 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
assert currentScope instanceof JsFunctionScope : "Usage of js outside of function is unexpected";
JsScope temporaryRootScope = new JsRootScope(new JsProgram());
JsScope scope = new DelegatingJsFunctionScopeWithTemporaryParent((JsFunctionScope) currentScope, temporaryRootScope);
return ParserUtilsKt.parse(jsCode, ThrowExceptionOnErrorReporter.INSTANCE, scope, jsCodeExpression.getContainingKtFile().getName());
JsLocation location;
try {
location = PsiUtils.extractLocationFromPsi(jsCodeExpression, context().getSourceFilePathResolver());
}
catch (IOException e) {
location = new JsLocation(jsCodeExpression.getContainingKtFile().getName(), 0, 0);
}
List<JsStatement> statements = ParserUtilsKt.parseExpressionOrStatement(
jsCode, ThrowExceptionOnErrorReporter.INSTANCE, scope,
new CodePosition(location.getStartLine(), location.getStartChar()), location.getFile());
return statements != null ? statements : Collections.emptyList();
}
}
@@ -16,18 +16,26 @@
package org.jetbrains.kotlin.js.translate.utils;
import com.intellij.openapi.editor.Document;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.js.backend.ast.JsLocation;
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver;
import org.jetbrains.kotlin.lexer.KtToken;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
public final class PsiUtils {
@@ -120,4 +128,18 @@ public final class PsiUtils {
return resolvedCall.getCandidateDescriptor();
}
@NotNull
public static JsLocation extractLocationFromPsi(@NotNull PsiElement element, @NotNull SourceFilePathResolver pathResolver)
throws IOException {
PsiFile psiFile = element.getContainingFile();
int offset = element.getNode().getStartOffset();
Document document = psiFile.getViewProvider().getDocument();
assert document != null;
int sourceLine = document.getLineNumber(offset);
int sourceColumn = offset - document.getLineStartOffset(sourceLine);
File file = new File(psiFile.getViewProvider().getVirtualFile().getPath());
return new JsLocation(pathResolver.getPathRelativeToSourceRoots(file), sourceLine, sourceColumn);
}
}
@@ -0,0 +1,26 @@
// EXPECTED_REACHABLE_NODES: 1026
import kotlin.js.*
private fun isOrdinaryObject(o: Any?): Boolean = jsTypeOf(o) == "object" && Object.getPrototypeOf(o).`constructor` === Any::class.js
external class Object {
companion object {
fun getPrototypeOf(x: Any?): dynamic
fun keys(x: Any): Array<String>
}
}
fun box(): String {
val a = js("{}")
if (!isOrdinaryObject(a)) return "fail: a is not an object"
if (Object.keys(a).size != 0) return "fail: a should not have any properties"
val b = js("{ foo: 23, bar: 42 }")
if (!isOrdinaryObject(b)) return "fail: b is not an object"
if (Object.keys(b).size != 2) return "fail: b should have two properties"
if (b.foo != 23) return "fail: b.foo == ${b.foo}"
if (b.bar != 42) return "fail: b.bar == ${b.bar}"
return "OK"
}
+8
View File
@@ -0,0 +1,8 @@
fun foo() {
var x = "foo"
println("before: $x")
js("console.log('js' + x);")
println("after: $x")
}
// LINES: 6 2 2 3 3 4 4 5 5