JS: refactor code that exports local declaration from current module

This commit is contained in:
Alexey Andreev
2016-10-25 16:28:39 +03:00
parent f244bbaae3
commit cc38bd3146
5 changed files with 134 additions and 189 deletions
@@ -20,9 +20,7 @@ import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.check
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.js.inline.util.CollectUtilsKt;
import java.util.Map;
import static org.jetbrains.kotlin.js.inline.util.CollectUtilsKt.collectNamedFunctions;
import static org.jetbrains.kotlin.js.inline.util.CollectUtilsKt.collectJsProperties;
public class AstSearchUtil {
@NotNull
@@ -37,13 +36,6 @@ public class AstSearchUtil {
return function;
}
@NotNull
public static JsExpression getProperty(@NotNull JsNode searchRoot, @NotNull String name) {
JsExpression property = findByIdent(collectJsProperties(searchRoot), name);
assert property != null: "Property `" + name + "` was not found";
return property;
}
@NotNull
public static JsExpression getMetadataOrFunction(@NotNull JsNode searchRoot, @NotNull String name) {
JsExpression property = findByIdent(CollectUtilsKt.collectNamedFunctionsOrMetadata(searchRoot), name);
@@ -0,0 +1,131 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.context
import com.google.dart.compiler.backend.js.ast.*
import com.google.dart.compiler.backend.js.ast.metadata.staticRef
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.assignment
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
internal class DeclarationExporter(val context: StaticContext) {
private val objectLikeKinds = setOf(ClassKind.OBJECT, ClassKind.ENUM_ENTRY)
private val exportedDeclarations = mutableSetOf<MemberDescriptor>()
private val localPackageNames = mutableMapOf<FqName, JsName>()
val statements = mutableListOf<JsStatement>()
fun export(descriptor: MemberDescriptor, force: Boolean) {
if (exportedDeclarations.contains(descriptor)) return
if (descriptor is ConstructorDescriptor && descriptor.isPrimary) return
val suggestedName = context.nameSuggestion.suggest(descriptor) ?: return
val container = suggestedName.scope
if (!descriptor.shouldBeExported(force)) return
exportedDeclarations.add(descriptor)
val qualifier = when (container) {
is PackageFragmentDescriptor -> getLocalPackageReference(container.fqName)
else -> context.getInnerNameForDescriptor(container).makeRef()
}
when {
descriptor is ClassDescriptor && descriptor.kind in objectLikeKinds -> {
exportObject(descriptor, qualifier)
}
descriptor is PropertyDescriptor && container is PackageFragmentDescriptor -> {
exportProperty(descriptor, qualifier)
}
else -> {
assign(descriptor, qualifier, context.getInnerNameForDescriptor(descriptor).makeRef())
}
}
}
private fun assign(descriptor: DeclarationDescriptor, qualifier: JsExpression, expression: JsExpression) {
val propertyName = context.getNameForDescriptor(descriptor)
if (propertyName.staticRef == null) {
if (expression !is JsNameRef || expression.name !== propertyName) {
propertyName.staticRef = expression
}
}
statements += assignment(JsNameRef(propertyName, qualifier), expression).makeStmt()
}
private fun exportObject(declaration: ClassDescriptor, qualifier: JsExpression) {
val name = context.getNameForDescriptor(declaration)
statements += JsAstUtils.defineGetter(context.program, qualifier, name.ident,
context.getNameForObjectInstance(declaration).makeRef())
}
private fun exportProperty(declaration: PropertyDescriptor, qualifier: JsExpression) {
val propertyLiteral = JsObjectLiteral(true)
val name = context.getNameForDescriptor(declaration).ident
val getterBody: JsExpression = if (JsDescriptorUtils.isSimpleFinalProperty(declaration)) {
val accessToField = JsReturn(context.getInnerNameForDescriptor(declaration).makeRef())
JsFunction(context.rootFunction.scope, JsBlock(accessToField), "$declaration getter")
}
else {
context.getInnerNameForDescriptor(declaration.getter!!).makeRef()
}
propertyLiteral.propertyInitializers += JsPropertyInitializer(JsNameRef("get"), getterBody)
if (declaration.isVar) {
val setterBody: JsExpression = if (JsDescriptorUtils.isSimpleFinalProperty(declaration)) {
val statements = mutableListOf<JsStatement>()
val function = JsFunction(context.rootFunction.scope, JsBlock(statements), "$declaration setter")
val valueName = function.scope.declareFreshName("value")
function.parameters += JsParameter(valueName)
statements += assignment(context.getInnerNameForDescriptor(declaration).makeRef(), valueName.makeRef()).makeStmt()
function
}
else {
context.getInnerNameForDescriptor(declaration.setter!!).makeRef()
}
propertyLiteral.propertyInitializers += JsPropertyInitializer(JsNameRef("set"), setterBody)
}
statements += JsAstUtils.defineProperty(qualifier, name, propertyLiteral, context.program).makeStmt()
}
private fun getLocalPackageReference(packageName: FqName): JsExpression {
if (packageName.isRoot) {
return context.rootFunction.scope.declareName(Namer.getRootPackageName()).makeRef()
}
var name = localPackageNames[packageName]
if (name == null) {
name = context.rootFunction.scope.declareFreshName("package$" + packageName.shortName().asString())
localPackageNames.put(packageName, name)
val parentRef = getLocalPackageReference(packageName.parent())
val selfRef = JsNameRef(packageName.shortName().asString(), parentRef)
val rhs = JsAstUtils.or(selfRef, assignment(selfRef.deepCopy(), JsObjectLiteral(false)))
statements.add(JsAstUtils.newVar(name, rhs))
}
return name.makeRef()
}
}
private fun MemberDescriptor.shouldBeExported(force: Boolean) =
force || isEffectivelyPublicApi || AnnotationsUtils.getJsNameAnnotation(this) != null
@@ -139,17 +139,11 @@ public final class StaticContext {
private final List<JsStatement> importStatements = new ArrayList<JsStatement>();
@NotNull
private final List<JsStatement> exportStatements = new ArrayList<JsStatement>();
private final DeclarationExporter exporter = new DeclarationExporter(this);
@NotNull
private final Set<ClassDescriptor> classes = new HashSet<ClassDescriptor>();
@NotNull
private final Set<MemberDescriptor> exportedDeclarations = new HashSet<MemberDescriptor>();
@NotNull
private final Map<FqName, JsName> localPackageNames = new HashMap<FqName, JsName>();
//TODO: too many parameters in constructor
private StaticContext(
@NotNull JsProgram program,
@@ -649,53 +643,7 @@ public final class StaticContext {
}
public void export(@NotNull MemberDescriptor descriptor, boolean force) {
if (exportedDeclarations.contains(descriptor)) return;
if (descriptor instanceof ConstructorDescriptor && ((ConstructorDescriptor) descriptor).isPrimary()) return;
SuggestedName suggestedName = nameSuggestion.suggest(descriptor);
if (suggestedName == null) return;
DeclarationDescriptor container = suggestedName.getScope();
if (!DeclarationExporter.shouldBeExported(descriptor, force)) return;
exportedDeclarations.add(descriptor);
if (container instanceof PackageFragmentDescriptor) {
JsExpression packageRef = getLocalPackageReference(((PackageFragmentDescriptor) container).getFqName());
JsExpression initializerExpr = DeclarationExporter.exportDeclaration(this, descriptor, exportStatements);
if (initializerExpr != null) {
JsName propertyName = getNameForDescriptor(descriptor);
if (MetadataProperties.getStaticRef(propertyName) == null) {
if (!(initializerExpr instanceof JsNameRef) || ((JsNameRef) initializerExpr).getName() != propertyName) {
MetadataProperties.setStaticRef(propertyName, initializerExpr);
}
}
exportStatements.add(JsAstUtils.assignment(new JsNameRef(propertyName, packageRef), initializerExpr).makeStmt());
}
}
else {
JsExpression initializerExpr = DeclarationExporter.exportDeclaration(this, descriptor, exportStatements);
assert initializerExpr == null : "Should not produce initializer for non-package declaration";
}
}
private JsExpression getLocalPackageReference(FqName packageName) {
if (packageName.isRoot()) {
return rootFunction.getScope().declareName(Namer.getRootPackageName()).makeRef();
}
JsName name = localPackageNames.get(packageName);
if (name == null) {
name = rootFunction.getScope().declareFreshName("package$" + packageName.shortName().asString());
localPackageNames.put(packageName, name);
JsExpression parentRef = getLocalPackageReference(packageName.parent());
JsExpression selfRef = new JsNameRef(packageName.shortName().asString(), parentRef);
JsExpression rhs = JsAstUtils.or(selfRef, JsAstUtils.assignment(selfRef.deepCopy(), new JsObjectLiteral(false)));
exportStatements.add(JsAstUtils.newVar(name, rhs));
}
return name.makeRef();
exporter.export(descriptor, force);
}
@NotNull
@@ -713,7 +661,7 @@ public final class StaticContext {
rootFunction.getBody().getStatements().addAll(importStatements);
addClassPrototypes();
rootFunction.getBody().getStatements().addAll(declarationStatements);
rootFunction.getBody().getStatements().addAll(exportStatements);
rootFunction.getBody().getStatements().addAll(exporter.getStatements());
rootFunction.getBody().getStatements().addAll(topLevelStatements);
}
@@ -1,124 +0,0 @@
/*
* Copyright 2010-2016 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.
*/
@file:JvmName("DeclarationExporter")
package org.jetbrains.kotlin.js.translate.context
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
// TODO: refactor
fun StaticContext.exportDeclaration(declaration: MemberDescriptor, additionalStatements: MutableList<in JsStatement>): JsExpression? {
val scope = nameSuggestion.suggest(declaration)!!.scope
return when {
declaration is ClassDescriptor -> when {
declaration.kind == ClassKind.OBJECT || declaration.kind == ClassKind.ENUM_ENTRY -> {
exportObject(declaration, scope, additionalStatements)
null
}
scope is ClassDescriptor -> {
exportNested(declaration, scope, additionalStatements)
null
}
else -> {
getInnerNameForDescriptor(declaration).makeRef()
}
}
declaration is PropertyDescriptor -> {
if (scope is PackageFragmentDescriptor) {
exportProperty(declaration, scope, additionalStatements)
}
null
}
scope is PackageFragmentDescriptor -> {
getInnerNameForDescriptor(declaration).makeRef()
}
else -> null
}
}
fun MemberDescriptor.shouldBeExported(force: Boolean) =
force || isEffectivelyPublicApi || AnnotationsUtils.getJsNameAnnotation(this) != null
private fun StaticContext.exportNested(
descriptor: ClassDescriptor,
scope: ClassDescriptor,
additionalStatements: MutableList<in JsStatement>
) {
val qualifier = getBaseReference(scope)
val reference = JsAstUtils.pureFqn(getNameForDescriptor(descriptor), qualifier)
val instanceRef = JsAstUtils.pureFqn(getInnerNameForDescriptor(descriptor), null)
additionalStatements += JsAstUtils.assignment(reference, instanceRef).makeStmt()
}
private fun StaticContext.exportObject(
declaration: ClassDescriptor,
scope: DeclarationDescriptor,
additionalStatements: MutableList<in JsStatement>
) {
val qualifier = getBaseReference(scope)
val name = getNameForDescriptor(declaration)
additionalStatements += JsAstUtils.defineGetter(program, qualifier, name.ident, getNameForObjectInstance(declaration).makeRef())
}
private fun StaticContext.getBaseReference(declaration: DeclarationDescriptor) = when (declaration) {
is PackageFragmentDescriptor -> getQualifiedReference(declaration)
else -> JsAstUtils.pureFqn(getInnerNameForDescriptor(declaration), null)
}
private fun StaticContext.exportProperty(
declaration: PropertyDescriptor,
scope: DeclarationDescriptor,
additionalStatements: MutableList<in JsStatement>
) {
val propertyLiteral = JsObjectLiteral(true)
val qualifier = getBaseReference(scope)
val name = getNameForDescriptor(declaration).ident
val getterBody: JsExpression = if (JsDescriptorUtils.isSimpleFinalProperty(declaration)) {
val accessToField = JsReturn(getInnerNameForDescriptor(declaration).makeRef())
JsFunction(rootFunction.scope, JsBlock(accessToField), "$declaration getter")
}
else {
getInnerNameForDescriptor(declaration.getter!!).makeRef()
}
propertyLiteral.propertyInitializers += JsPropertyInitializer(JsNameRef("get"), getterBody)
if (declaration.isVar) {
val setterBody: JsExpression = if (JsDescriptorUtils.isSimpleFinalProperty(declaration)) {
val statements = mutableListOf<JsStatement>()
val function = JsFunction(rootFunction.scope, JsBlock(statements), "$declaration setter")
val valueName = function.scope.declareFreshName("value")
function.parameters += JsParameter(valueName)
statements += JsAstUtils.assignment(getInnerNameForDescriptor(declaration).makeRef(), valueName.makeRef()).makeStmt()
function
}
else {
getInnerNameForDescriptor(declaration.setter!!).makeRef()
}
propertyLiteral.propertyInitializers += JsPropertyInitializer(JsNameRef("set"), setterBody)
}
additionalStatements += JsAstUtils.defineProperty(qualifier, name, propertyLiteral, program).makeStmt()
}