JS: incremental translation of tests and main fun invocation

This commit is contained in:
Anton Bannykh
2018-11-13 21:07:38 +03:00
parent 0397af79b4
commit 3529114913
12 changed files with 1134 additions and 86 deletions
@@ -158,11 +158,12 @@ public final class StaticContext {
@NotNull BindingTrace bindingTrace,
@NotNull JsConfig config,
@NotNull ModuleDescriptor moduleDescriptor,
@NotNull SourceFilePathResolver sourceFilePathResolver
@NotNull SourceFilePathResolver sourceFilePathResolver,
@NotNull String packageFqn
) {
program = new JsProgram();
JsFunction rootFunction = JsAstUtils.createFunctionWithEmptyBody(program.getScope());
fragment = new JsProgramFragment(rootFunction.getScope());
fragment = new JsProgramFragment(rootFunction.getScope(), packageFqn);
this.bindingTrace = bindingTrace;
this.namer = Namer.newInstance(program.getRootScope());
@@ -34,6 +34,8 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
val importBlock = JsGlobalBlock()
private val declarationBlock = JsGlobalBlock()
private val initializerBlock = JsGlobalBlock()
private val testsMap = mutableMapOf<String, JsStatement>()
private var mainFn: Pair<String, JsStatement>? = null
private val exportBlock = JsGlobalBlock()
private val declaredImports = mutableSetOf<String>()
private val classes = mutableMapOf<JsName, JsClassModel>()
@@ -55,11 +57,42 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
declarationBlock.statements += fragment.declarationBlock
initializerBlock.statements += fragment.initializerBlock
fragment.tryUpdateTests()
fragment.tryUpdateMain()
addExportStatements(fragment)
classes += fragment.classes
}
private fun JsProgramFragment.tryUpdateTests() {
tests?.let { newTests ->
testsMap.putIfAbsent(packageFqn, newTests)?.let { oldTests ->
oldTests.decomposeTestInvocation()?.let {oldTestBody ->
newTests.decomposeTestInvocation()?.let { newTestBody ->
oldTestBody.statements += newTestBody.statements
}
}
}
}
}
private fun JsStatement.decomposeTestInvocation(): JsBlock? {
return (this as? JsExpressionStatement)?.let {
(it.expression as? JsInvocation)?.let {
(it.arguments[2] as? JsFunction)?.body
}
}
}
private fun JsProgramFragment.tryUpdateMain() {
mainFunction?.let { m ->
val currentMainFqn = mainFn?.first
if (currentMainFqn == null || currentMainFqn > packageFqn) {
mainFn = packageFqn to m
}
}
}
val importedModules: List<JsImportedModule>
get() = importedModulesImpl
@@ -136,6 +169,9 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
fragment.classes += classes.map { it.name to it }
fragment.inlineModuleMap.forEach { (_, value) -> rename(value) }
fragment.tests?.let { rename(it) }
fragment.mainFunction?.let { rename(it) }
}
private fun <T: JsNode> Map<JsName, JsName>.rename(rootNode: T): T {
@@ -174,6 +210,8 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
this += exportBlock.statements
addClassPostDeclarations(this)
this += initializerBlock.statements
this += testsMap.values
mainFn?.second?.let { this += it }
}
}
@@ -340,15 +340,21 @@ 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, sourceFilePathResolver);
StaticContext staticContext = new StaticContext(bindingTrace, config, moduleDescriptor, sourceFilePathResolver, file.getPackageFqName().asString());
TranslationContext context = TranslationContext.rootContext(staticContext);
List<DeclarationDescriptor> fileMemberScope = new ArrayList<>();
translateFile(context, file, fileMemberScope);
fragments.add(staticContext.getFragment());
newFragments.add(staticContext.getFragment());
fragmentMap.put(file, staticContext.getFragment());
JsProgramFragment fragment = staticContext.getFragment();
fragment.setTests(mayBeGenerateTests(context, file, fileMemberScope));
fragment.setMainFunction(maybeGenerateCallToMain(context, config, moduleDescriptor, fileMemberScope, mainCallParameters));
fragments.add(fragment);
newFragments.add(fragment);
fragmentMap.put(file, fragment);
fileMemberScopes.put(file, fileMemberScope);
merger.addFragment(staticContext.getFragment());
merger.addFragment(fragment);
}
else if (unit instanceof TranslationUnit.BinaryAst) {
byte[] astData = ((TranslationUnit.BinaryAst) unit).getData();
@@ -358,22 +364,8 @@ public final class Translation {
}
}
JsProgramFragment testFragment = mayBeGenerateTests(config, bindingTrace, moduleDescriptor, sourceFilePathResolver);
fragments.add(testFragment);
newFragments.add(testFragment);
merger.addFragment(testFragment);
rootFunction.getParameters().add(new JsParameter(internalModuleName));
if (mainCallParameters.shouldBeGenerated()) {
JsProgramFragment mainCallFragment = generateCallToMain(
bindingTrace, config, moduleDescriptor, sourceFilePathResolver, mainCallParameters.arguments());
if (mainCallFragment != null) {
fragments.add(mainCallFragment);
newFragments.add(mainCallFragment);
merger.addFragment(mainCallFragment);
}
}
merger.merge();
JsBlock rootBlock = rootFunction.getBody();
@@ -447,30 +439,36 @@ public final class Translation {
}
}
@NotNull
private static JsProgramFragment mayBeGenerateTests(
@NotNull JsConfig config, @NotNull BindingTrace trace,
@NotNull ModuleDescriptor moduleDescriptor, @NotNull SourceFilePathResolver sourceFilePathResolver
@Nullable
private static JsStatement mayBeGenerateTests(
@NotNull TranslationContext context,
@NotNull KtFile file,
@NotNull List<DeclarationDescriptor> fileMemberScope
) {
StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor, sourceFilePathResolver);
TranslationContext context = TranslationContext.rootContext(staticContext);
new JSTestGenerator(context).generateTestCalls(moduleDescriptor);
return staticContext.getFragment();
return new JSTestGenerator(context).generateTestCalls(file, fileMemberScope);
}
//TODO: determine whether should throw exception
@Nullable
private static JsProgramFragment generateCallToMain(
@NotNull BindingTrace trace, @NotNull JsConfig config, @NotNull ModuleDescriptor moduleDescriptor,
@NotNull SourceFilePathResolver sourceFilePathResolver,
@NotNull List<String> arguments
private static JsStatement maybeGenerateCallToMain(
@NotNull TranslationContext context,
@NotNull JsConfig config,
@NotNull ModuleDescriptor moduleDescriptor,
@NotNull List<DeclarationDescriptor> fileMemberScope,
@NotNull MainCallParameters mainCallParameters
) {
StaticContext staticContext = new StaticContext(trace, config, moduleDescriptor, sourceFilePathResolver);
TranslationContext context = TranslationContext.rootContext(staticContext);
if (!mainCallParameters.shouldBeGenerated()) return null;
MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(context.bindingContext(), config.getLanguageVersionSettings());
FunctionDescriptor functionDescriptor = mainFunctionDetector.getMainFunction(moduleDescriptor);
FunctionDescriptor functionDescriptor = null;
for (DeclarationDescriptor d : fileMemberScope) {
if (mainFunctionDetector.isMain(d)) {
functionDescriptor = (FunctionDescriptor)d;
}
}
if (functionDescriptor == null) {
return null;
}
@@ -480,7 +478,7 @@ public final class Translation {
List<JsExpression> args = new ArrayList<>();
if (parameterCount != 0) {
args.add(new JsArrayLiteral(toStringLiteralList(arguments)));
args.add(new JsArrayLiteral(toStringLiteralList(mainCallParameters.arguments())));
}
if (functionDescriptor.isSuspend()) {
@@ -492,7 +490,6 @@ public final class Translation {
}
JsExpression call = CallTranslator.INSTANCE.buildCall(context, functionDescriptor, args, null);
context.addTopLevelStatement(call.makeStmt());
return staticContext.getFragment();
return call.makeStmt();
}
}
@@ -24,14 +24,25 @@ import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
class JSTestGenerator(val context: TranslationContext) {
fun generateTestCalls(moduleDescriptor: ModuleDescriptor) {
generateTestCalls(moduleDescriptor, FqName.ROOT)
fun generateTestCalls(file: KtFile, fileMemberScope: List<DeclarationDescriptor>): JsStatement? {
val testsFunction = JsFunction(context.scope(), JsBlock(), "${file.virtualFilePath} file suite function")
fileMemberScope.forEach {
if (it is ClassDescriptor) {
generateTestFunctions(it, testsFunction)
}
}
if (!testsFunction.body.isEmpty) {
val suiteName = JsStringLiteral(file.packageFqName.asString())
return JsInvocation(suiteRef, suiteName, JsBooleanLiteral(false), testsFunction).makeStmt()
}
return null
}
private fun generateTestCalls(moduleDescriptor: ModuleDescriptor, packageName: FqName) {