J2K diagnostic tests: convert to Kotlin

This commit is contained in:
Alexander Udalov
2016-12-19 13:59:23 +03:00
parent 4e4f9312bb
commit 8c0be58f56
3 changed files with 762 additions and 939 deletions
@@ -14,322 +14,275 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.checkers; package org.jetbrains.kotlin.checkers
import com.google.common.base.Predicate; import com.google.common.base.Predicate
import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets
import com.google.common.collect.Sets; import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.GlobalSearchScope; import junit.framework.TestCase
import junit.framework.TestCase; import org.jetbrains.kotlin.analyzer.AnalysisResult
import kotlin.TuplesKt; import org.jetbrains.kotlin.analyzer.common.DefaultAnalyzerFacade
import kotlin.collections.CollectionsKt; import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
import kotlin.collections.MapsKt; import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
import kotlin.jvm.functions.Function1; import org.jetbrains.kotlin.config.CommonConfigurationKeys
import kotlin.jvm.functions.Function2; import org.jetbrains.kotlin.config.CompilerConfiguration
import kotlin.text.StringsKt; import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.container.getService
import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.context.*
import org.jetbrains.kotlin.analyzer.ModuleContent; import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.analyzer.ModuleInfo; import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
import org.jetbrains.kotlin.analyzer.common.DefaultAnalyzerFacade; import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport; import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider; import org.jetbrains.kotlin.diagnostics.Errors.*
import org.jetbrains.kotlin.config.CommonConfigurationKeys; import org.jetbrains.kotlin.frontend.java.di.createContainerForTopDownAnalyzerForJvm
import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.frontend.java.di.initJvmBuiltInsForTopDownAnalysis
import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl; import org.jetbrains.kotlin.load.java.lazy.SingleModuleClassResolver
import org.jetbrains.kotlin.container.ComponentProvider; import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.container.DslKt; import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.context.ContextKt; import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.context.GlobalContext; import org.jetbrains.kotlin.platform.JvmBuiltIns
import org.jetbrains.kotlin.context.ModuleContext; import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.context.SimpleGlobalContext; import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider; import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.diagnostics.*; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.frontend.java.di.InjectionKt; import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
import org.jetbrains.kotlin.incremental.components.LookupTracker; import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.load.java.lazy.SingleModuleClassResolver; import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.storage.ExceptionTracker
import org.jetbrains.kotlin.name.SpecialNames; import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.platform.JvmBuiltIns; import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.psi.Call; import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.psi.KtElement; import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.test.util.DescriptorValidator
import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator
import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.*
import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall; import org.jetbrains.kotlin.utils.rethrow
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.junit.Assert
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; import java.io.File
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics; import java.util.*
import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver;
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer;
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory;
import org.jetbrains.kotlin.storage.ExceptionTracker;
import org.jetbrains.kotlin.storage.LockBasedStorageManager;
import org.jetbrains.kotlin.storage.StorageManager;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.util.DescriptorValidator;
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.junit.Assert;
import java.io.File; abstract class AbstractDiagnosticsTest : BaseDiagnosticsTest() {
import java.util.*; override fun analyzeAndCheck(testDataFile: File, testFiles: List<BaseDiagnosticsTest.TestFile>) {
val groupedByModule = testFiles.groupByTo<TestFile, TestModule, LinkedHashMap<TestModule, List<TestFile>>>(
LinkedHashMap<TestModule, List<TestFile>>()
) { file -> file.module }
import static org.jetbrains.kotlin.diagnostics.Errors.*; val checkLazyResolveLog = testFiles.any { file -> file.checkLazyLog }
import static org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.RECURSIVE;
import static org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.RECURSIVE_ALL;
public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest { var lazyOperationsLog: LazyOperationsLog? = null
val context: GlobalContext
private static final Function1<String, String> HASH_SANITIZER = new Function1<String, String>() { val tracker = ExceptionTracker()
@Override
public String invoke(String s) {
return s.replaceAll("@(\\d)+", "");
}
};
private static final ModuleDescriptor.Capability<List<KtFile>> MODULE_FILES = new ModuleDescriptor.Capability<List<KtFile>>("");
@Override
protected void analyzeAndCheck(File testDataFile, List<TestFile> testFiles) {
Map<TestModule, List<TestFile>> groupedByModule = CollectionsKt.groupByTo(
testFiles,
new LinkedHashMap<TestModule, List<TestFile>>(),
new Function1<TestFile, TestModule>() {
@Override
public TestModule invoke(TestFile file) {
return file.getModule();
}
}
);
boolean checkLazyResolveLog = CollectionsKt.any(testFiles, new Function1<TestFile, Boolean>() {
@Override
public Boolean invoke(TestFile file) {
return file.checkLazyLog;
}
});
LazyOperationsLog lazyOperationsLog = null;
GlobalContext context;
ExceptionTracker tracker = new ExceptionTracker();
if (checkLazyResolveLog) { if (checkLazyResolveLog) {
lazyOperationsLog = new LazyOperationsLog(HASH_SANITIZER); lazyOperationsLog = LazyOperationsLog(HASH_SANITIZER)
context = new SimpleGlobalContext( context = SimpleGlobalContext(
new LoggingStorageManager( LoggingStorageManager(
LockBasedStorageManager.createWithExceptionHandling(tracker), LockBasedStorageManager.createWithExceptionHandling(tracker),
lazyOperationsLog.getAddRecordFunction() lazyOperationsLog.addRecordFunction
), ),
tracker tracker
); )
} }
else { else {
context = new SimpleGlobalContext(LockBasedStorageManager.createWithExceptionHandling(tracker), tracker); context = SimpleGlobalContext(LockBasedStorageManager.createWithExceptionHandling(tracker), tracker)
} }
Map<TestModule, ModuleDescriptorImpl> modules = createModules(groupedByModule, context.getStorageManager()); val modules = createModules(groupedByModule, context.storageManager)
Map<TestModule, BindingContext> moduleBindings = new HashMap<TestModule, BindingContext>(); val moduleBindings = java.util.HashMap<TestModule, BindingContext>()
for (Map.Entry<TestModule, List<TestFile>> entry : groupedByModule.entrySet()) { for ((testModule, testFilesInModule) in groupedByModule) {
TestModule testModule = entry.getKey();
List<? extends TestFile> testFilesInModule = entry.getValue();
List<KtFile> jetFiles = getJetFiles(testFilesInModule, true); val jetFiles = getJetFiles(testFilesInModule, true)
ModuleDescriptorImpl oldModule = modules.get(testModule); val oldModule = modules.get(testModule)
LanguageVersionSettings languageVersionSettings = loadLanguageVersionSettings(testFilesInModule); val languageVersionSettings = loadLanguageVersionSettings(testFilesInModule)
ModuleContext moduleContext = ContextKt.withModule(ContextKt.withProject(context, getProject()), oldModule); val moduleContext = context.withProject(project).withModule(oldModule)
boolean separateModules = groupedByModule.size() == 1 && groupedByModule.keySet().iterator().next() == null; val separateModules = groupedByModule.size == 1 && groupedByModule.keys.iterator().next() == null
AnalysisResult result = analyzeModuleContents( val result = analyzeModuleContents(
moduleContext, jetFiles, new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(), moduleContext, jetFiles, CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(),
languageVersionSettings, separateModules languageVersionSettings, separateModules
); )
ModuleDescriptorImpl newModule = (ModuleDescriptorImpl) result.getModuleDescriptor(); if (oldModule != result.moduleDescriptor) {
if (oldModule != newModule) {
// For common modules, we use DefaultAnalyzerFacade who creates ModuleDescriptor instances by itself // For common modules, we use DefaultAnalyzerFacade who creates ModuleDescriptor instances by itself
// (its API does not support working with a module created beforehand). // (its API does not support working with a module created beforehand).
// So, we should replace the old (effectively discarded) module with the new one everywhere in dependencies. // So, we should replace the old (effectively discarded) module with the new one everywhere in dependencies.
// TODO: dirty hack, refactor this test so that it doesn't create ModuleDescriptor instances // TODO: dirty hack, refactor this test so that it doesn't create ModuleDescriptor instances
modules.put(testModule, newModule); modules.put(testModule, result.moduleDescriptor)
for (ModuleDescriptorImpl module : modules.values()) { for (module in modules.values) {
@SuppressWarnings("deprecation") val it = module.testOnly_AllDependentModules.listIterator()
ListIterator<ModuleDescriptorImpl> it = module.getTestOnly_AllDependentModules().listIterator();
while (it.hasNext()) { while (it.hasNext()) {
if (it.next() == oldModule) { if (it.next() == oldModule) {
it.set(newModule); it.set(result.moduleDescriptor)
} }
} }
} }
} }
moduleBindings.put(testModule, result.getBindingContext()); moduleBindings.put(testModule, result.bindingContext)
checkAllResolvedCallsAreCompleted(jetFiles, result.getBindingContext()); checkAllResolvedCallsAreCompleted(jetFiles, result.bindingContext)
} }
// We want to always create a test data file (txt) if it was missing, // We want to always create a test data file (txt) if it was missing,
// but don't want to skip the following checks in case this one fails // but don't want to skip the following checks in case this one fails
Throwable exceptionFromLazyResolveLogValidation = null; var exceptionFromLazyResolveLogValidation: Throwable? = null
if (checkLazyResolveLog) { if (checkLazyResolveLog) {
exceptionFromLazyResolveLogValidation = checkLazyResolveLog(lazyOperationsLog, testDataFile); exceptionFromLazyResolveLogValidation = checkLazyResolveLog(lazyOperationsLog, testDataFile)
} }
else { else {
File lazyLogFile = getLazyLogFile(testDataFile); val lazyLogFile = getLazyLogFile(testDataFile)
TestCase.assertFalse("No lazy log expected, but found: " + lazyLogFile.getAbsolutePath(), lazyLogFile.exists()); TestCase.assertFalse("No lazy log expected, but found: " + lazyLogFile.getAbsolutePath(), lazyLogFile.exists())
} }
Throwable exceptionFromDescriptorValidation = null; var exceptionFromDescriptorValidation: Throwable? = null
try { try {
File expectedFile = new File(FileUtil.getNameWithoutExtension(testDataFile.getAbsolutePath()) + ".txt"); val expectedFile = File(FileUtil.getNameWithoutExtension(testDataFile.absolutePath) + ".txt")
validateAndCompareDescriptorWithFile(expectedFile, testFiles, modules); validateAndCompareDescriptorWithFile(expectedFile, testFiles, modules)
} }
catch (Throwable e) { catch (e: Throwable) {
exceptionFromDescriptorValidation = e; exceptionFromDescriptorValidation = e
} }
// main checks // main checks
boolean ok = true; var ok = true
StringBuilder actualText = new StringBuilder(); val actualText = StringBuilder()
for (TestFile testFile : testFiles) { for (testFile in testFiles) {
TestModule module = testFile.getModule(); val module = testFile.module
boolean isCommonModule = MultiTargetPlatformKt.getMultiTargetPlatform(modules.get(module)) == MultiTargetPlatform.Common.INSTANCE; val isCommonModule = modules.get(module).getMultiTargetPlatform() === MultiTargetPlatform.Common
ok &= testFile.getActualText( ok = ok and testFile.getActualText(
moduleBindings.get(module), actualText, moduleBindings.get(module), actualText,
shouldSkipJvmSignatureDiagnostics(groupedByModule) || isCommonModule shouldSkipJvmSignatureDiagnostics(groupedByModule) || isCommonModule
); )
} }
Throwable exceptionFromDynamicCallDescriptorsValidation = null; var exceptionFromDynamicCallDescriptorsValidation: Throwable? = null
try { try {
File expectedFile = new File(FileUtil.getNameWithoutExtension(testDataFile.getAbsolutePath()) + ".dynamic.txt"); val expectedFile = File(FileUtil.getNameWithoutExtension(testDataFile.absolutePath) + ".dynamic.txt")
checkDynamicCallDescriptors(expectedFile, testFiles); checkDynamicCallDescriptors(expectedFile, testFiles)
} }
catch (Throwable e) { catch (e: Throwable) {
exceptionFromDynamicCallDescriptorsValidation = e; exceptionFromDynamicCallDescriptorsValidation = e
} }
KotlinTestUtils.assertEqualsToFile(testDataFile, actualText.toString()); KotlinTestUtils.assertEqualsToFile(testDataFile, actualText.toString())
TestCase.assertTrue("Diagnostics mismatch. See the output above", ok); TestCase.assertTrue("Diagnostics mismatch. See the output above", ok)
// now we throw a previously found error, if any // now we throw a previously found error, if any
if (exceptionFromDescriptorValidation != null) { if (exceptionFromDescriptorValidation != null) {
throw ExceptionUtilsKt.rethrow(exceptionFromDescriptorValidation); throw rethrow(exceptionFromDescriptorValidation)
} }
if (exceptionFromLazyResolveLogValidation != null) { if (exceptionFromLazyResolveLogValidation != null) {
throw ExceptionUtilsKt.rethrow(exceptionFromLazyResolveLogValidation); throw rethrow(exceptionFromLazyResolveLogValidation)
} }
if (exceptionFromDynamicCallDescriptorsValidation != null) { if (exceptionFromDynamicCallDescriptorsValidation != null) {
throw ExceptionUtilsKt.rethrow(exceptionFromDynamicCallDescriptorsValidation); throw rethrow(exceptionFromDynamicCallDescriptorsValidation)
} }
performAdditionalChecksAfterDiagnostics(testDataFile, testFiles, groupedByModule, modules, moduleBindings); performAdditionalChecksAfterDiagnostics(testDataFile, testFiles, groupedByModule, modules, moduleBindings)
} }
protected void performAdditionalChecksAfterDiagnostics( protected fun performAdditionalChecksAfterDiagnostics(
File testDataFile, testDataFile: File,
List<TestFile> testFiles, testFiles: List<BaseDiagnosticsTest.TestFile>,
Map<TestModule, List<TestFile>> moduleFiles, moduleFiles: Map<BaseDiagnosticsTest.TestModule, List<BaseDiagnosticsTest.TestFile>>,
Map<TestModule, ModuleDescriptorImpl> moduleDescriptors, moduleDescriptors: Map<BaseDiagnosticsTest.TestModule, ModuleDescriptorImpl>,
Map<TestModule, BindingContext> moduleBindings moduleBindings: Map<BaseDiagnosticsTest.TestModule, BindingContext>
) { ) {
// To be overridden by diagnostic-like tests. // To be overridden by diagnostic-like tests.
} }
@Nullable private fun loadLanguageVersionSettings(module: List<BaseDiagnosticsTest.TestFile>): LanguageVersionSettings? {
private LanguageVersionSettings loadLanguageVersionSettings(List<? extends TestFile> module) { var result: LanguageVersionSettings? = null
LanguageVersionSettings result = null; for (file in module) {
for (TestFile file : module) { val current = file.customLanguageVersionSettings
LanguageVersionSettings current = file.customLanguageVersionSettings;
if (current != null) { if (current != null) {
if (result != null && !result.equals(current)) { if (result != null && result != current) {
Assert.fail( Assert.fail(
"More than one file in the module has " + BaseDiagnosticsTest.LANGUAGE_DIRECTIVE + " or " + "More than one file in the module has " + BaseDiagnosticsTest.LANGUAGE_DIRECTIVE + " or " +
BaseDiagnosticsTest.API_VERSION_DIRECTIVE + " directive specified. " + BaseDiagnosticsTest.API_VERSION_DIRECTIVE + " directive specified. " +
"This is not supported. Please move all directives into one file" "This is not supported. Please move all directives into one file"
); )
} }
result = current; result = current
} }
} }
return result; return result
} }
private void checkDynamicCallDescriptors(File expectedFile, List<TestFile> testFiles) { private fun checkDynamicCallDescriptors(expectedFile: File, testFiles: List<BaseDiagnosticsTest.TestFile>) {
RecursiveDescriptorComparator serializer = new RecursiveDescriptorComparator(RECURSIVE_ALL); val serializer = RecursiveDescriptorComparator(RECURSIVE_ALL)
StringBuilder actualText = new StringBuilder(); val actualText = StringBuilder()
for (TestFile testFile : testFiles) { for (testFile in testFiles) {
List<DeclarationDescriptor> dynamicCallDescriptors = testFile.getDynamicCallDescriptors(); val dynamicCallDescriptors = testFile.dynamicCallDescriptors
for (DeclarationDescriptor descriptor : dynamicCallDescriptors) { for (descriptor in dynamicCallDescriptors) {
String actualSerialized = serializer.serializeRecursively(descriptor); val actualSerialized = serializer.serializeRecursively(descriptor)
actualText.append(actualSerialized); actualText.append(actualSerialized)
} }
} }
if (actualText.length() != 0 || expectedFile.exists()) { if (actualText.length != 0 || expectedFile.exists()) {
KotlinTestUtils.assertEqualsToFile(expectedFile, actualText.toString()); KotlinTestUtils.assertEqualsToFile(expectedFile, actualText.toString())
} }
} }
public boolean shouldSkipJvmSignatureDiagnostics(Map<TestModule, List<TestFile>> groupedByModule) { fun shouldSkipJvmSignatureDiagnostics(groupedByModule: Map<BaseDiagnosticsTest.TestModule, List<BaseDiagnosticsTest.TestFile>>): Boolean {
return groupedByModule.size() > 1; return groupedByModule.size > 1
} }
@Nullable private fun checkLazyResolveLog(lazyOperationsLog: LazyOperationsLog, testDataFile: File): Throwable? {
private static Throwable checkLazyResolveLog(LazyOperationsLog lazyOperationsLog, File testDataFile) { var exceptionFromLazyResolveLogValidation: Throwable? = null
Throwable exceptionFromLazyResolveLogValidation = null;
try { try {
File expectedFile = getLazyLogFile(testDataFile); val expectedFile = getLazyLogFile(testDataFile)
KotlinTestUtils.assertEqualsToFile( KotlinTestUtils.assertEqualsToFile(
expectedFile, expectedFile,
lazyOperationsLog.getText(), lazyOperationsLog.getText(),
HASH_SANITIZER HASH_SANITIZER
); )
} }
catch (Throwable e) { catch (e: Throwable) {
exceptionFromLazyResolveLogValidation = e; exceptionFromLazyResolveLogValidation = e
}
return exceptionFromLazyResolveLogValidation;
} }
private static File getLazyLogFile(File testDataFile) { return exceptionFromLazyResolveLogValidation
return new File(FileUtil.getNameWithoutExtension(testDataFile.getAbsolutePath()) + ".lazy.log");
} }
@NotNull private fun getLazyLogFile(testDataFile: File): File {
protected AnalysisResult analyzeModuleContents( return File(FileUtil.getNameWithoutExtension(testDataFile.absolutePath) + ".lazy.log")
@NotNull ModuleContext moduleContext, }
@NotNull List<KtFile> files,
@NotNull BindingTrace moduleTrace, protected fun analyzeModuleContents(
@Nullable LanguageVersionSettings languageVersionSettings, moduleContext: ModuleContext,
boolean separateModules files: List<KtFile>,
) { moduleTrace: BindingTrace,
CompilerConfiguration configuration; languageVersionSettings: LanguageVersionSettings?,
separateModules: Boolean
): AnalysisResult {
var files = files
var languageVersionSettings = languageVersionSettings
val configuration: CompilerConfiguration
if (languageVersionSettings != null) { if (languageVersionSettings != null) {
configuration = getEnvironment().getConfiguration().copy(); configuration = environment.configuration.copy()
configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, languageVersionSettings); configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, languageVersionSettings)
} }
else { else {
configuration = getEnvironment().getConfiguration(); configuration = environment.configuration
languageVersionSettings = LanguageVersionSettingsImpl.DEFAULT; languageVersionSettings = LanguageVersionSettingsImpl.DEFAULT
} }
// New JavaDescriptorResolver is created for each module, which is good because it emulates different Java libraries for each module, // New JavaDescriptorResolver is created for each module, which is good because it emulates different Java libraries for each module,
@@ -341,300 +294,281 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest {
// TODO: always use separate module mode, once analyzeFilesWithJavaIntegration can create multiple modules // TODO: always use separate module mode, once analyzeFilesWithJavaIntegration can create multiple modules
if (separateModules) { if (separateModules) {
return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
moduleContext.getProject(), moduleContext.project,
files, files,
moduleTrace, moduleTrace,
configuration, configuration,
new Function1<GlobalSearchScope, PackagePartProvider>() { { scope -> JvmPackagePartProvider(environment, scope) }
@Override )
public PackagePartProvider invoke(GlobalSearchScope scope) {
return new JvmPackagePartProvider(getEnvironment(), scope);
}
}
);
} }
ModuleDescriptorImpl moduleDescriptor = (ModuleDescriptorImpl) moduleContext.getModule(); val moduleDescriptor = moduleContext.module as ModuleDescriptorImpl
MultiTargetPlatform platform = MultiTargetPlatformKt.getMultiTargetPlatform(moduleDescriptor); val platform = moduleDescriptor.getMultiTargetPlatform()
if (platform == MultiTargetPlatform.Common.INSTANCE) { if (platform === MultiTargetPlatform.Common) {
//noinspection unchecked
return DefaultAnalyzerFacade.INSTANCE.analyzeFiles( return DefaultAnalyzerFacade.analyzeFiles(
files, moduleDescriptor.getName(), true, files, moduleDescriptor.name, true,
MapsKt.mapOf( mapOf<ModuleDescriptor.Capability<out Any>, Any>(
TuplesKt.to(MultiTargetPlatform.CAPABILITY, MultiTargetPlatform.Common.INSTANCE), MultiTargetPlatform.CAPABILITY.to<ModuleDescriptor.Capability<MultiTargetPlatform>, MultiTargetPlatform.Common>(MultiTargetPlatform.Common),
TuplesKt.to(MODULE_FILES, files) MODULE_FILES.to<ModuleDescriptor.Capability<List<KtFile>>, List<KtFile>>(files)
), )
new Function2<ModuleInfo, ModuleContent, PackagePartProvider>() { ) { info, content ->
@Override
public PackagePartProvider invoke(ModuleInfo info, ModuleContent content) {
// TODO // TODO
return PackagePartProvider.Empty.INSTANCE; PackagePartProvider.Empty
} }
} }
);
}
else if (platform != null) { else if (platform != null) {
// TODO: analyze with the correct platform, not always JVM // TODO: analyze with the correct platform, not always JVM
files = CollectionsKt.plus(files, getCommonCodeFilesForPlatformSpecificModule(moduleDescriptor)); files = files.plus<KtFile>(getCommonCodeFilesForPlatformSpecificModule(moduleDescriptor))
} }
GlobalSearchScope moduleContentScope = GlobalSearchScope.allScope(moduleContext.getProject()); val moduleContentScope = GlobalSearchScope.allScope(moduleContext.project)
SingleModuleClassResolver moduleClassResolver = new SingleModuleClassResolver(); val moduleClassResolver = SingleModuleClassResolver()
ComponentProvider container = InjectionKt.createContainerForTopDownAnalyzerForJvm( val container = createContainerForTopDownAnalyzerForJvm(
moduleContext, moduleContext,
moduleTrace, moduleTrace,
new FileBasedDeclarationProviderFactory(moduleContext.getStorageManager(), files), FileBasedDeclarationProviderFactory(moduleContext.storageManager, files),
moduleContentScope, moduleContentScope,
LookupTracker.Companion.getDO_NOTHING(), LookupTracker.DO_NOTHING,
new JvmPackagePartProvider(getEnvironment(), moduleContentScope), JvmPackagePartProvider(environment, moduleContentScope),
languageVersionSettings, languageVersionSettings,
moduleClassResolver moduleClassResolver
); )
InjectionKt.initJvmBuiltInsForTopDownAnalysis(container, moduleDescriptor, languageVersionSettings); container.initJvmBuiltInsForTopDownAnalysis(moduleDescriptor, languageVersionSettings)
moduleClassResolver.setResolver(DslKt.getService(container, JavaDescriptorResolver.class)); moduleClassResolver.resolver = container.getService(JavaDescriptorResolver::class.java)
moduleDescriptor.initialize(new CompositePackageFragmentProvider(Arrays.asList( moduleDescriptor.initialize(CompositePackageFragmentProvider(Arrays.asList<PackageFragmentProvider>(
DslKt.getService(container, KotlinCodeAnalyzer.class).getPackageFragmentProvider(), container.getService(KotlinCodeAnalyzer::class.java).getPackageFragmentProvider(),
DslKt.getService(container, JavaDescriptorResolver.class).getPackageFragmentProvider() container.getService(JavaDescriptorResolver::class.java).packageFragmentProvider
))); )))
DslKt.getService(container, LazyTopDownAnalyzer.class).analyzeDeclarations( container.getService(LazyTopDownAnalyzer::class.java).analyzeDeclarations(
TopDownAnalysisMode.TopLevelDeclarations, files, DataFlowInfo.Companion.getEMPTY() TopDownAnalysisMode.TopLevelDeclarations, files, DataFlowInfo.EMPTY
); )
return AnalysisResult.success(moduleTrace.getBindingContext(), moduleDescriptor); return AnalysisResult.success(moduleTrace.bindingContext, moduleDescriptor)
} }
@NotNull private fun getCommonCodeFilesForPlatformSpecificModule(moduleDescriptor: ModuleDescriptorImpl): List<KtFile> {
private static List<KtFile> getCommonCodeFilesForPlatformSpecificModule(@NotNull ModuleDescriptorImpl moduleDescriptor) {
// We assume that a platform-specific module _implements_ all declarations from common modules which are immediate dependencies. // We assume that a platform-specific module _implements_ all declarations from common modules which are immediate dependencies.
// So we collect all sources from such modules to analyze in the platform-specific module as well // So we collect all sources from such modules to analyze in the platform-specific module as well
@SuppressWarnings("deprecation") val dependencies = moduleDescriptor.testOnly_AllDependentModules
List<ModuleDescriptorImpl> dependencies = moduleDescriptor.getTestOnly_AllDependentModules();
// TODO: diagnostics on common code reported during the platform module analysis should be distinguished somehow // TODO: diagnostics on common code reported during the platform module analysis should be distinguished somehow
// E.g. "<!JVM:IMPLEMENTATION_WITHOUT_HEADER!>...<!> // E.g. "<!JVM:IMPLEMENTATION_WITHOUT_HEADER!>...<!>
List<KtFile> result = new ArrayList<KtFile>(0); val result = ArrayList<KtFile>(0)
for (ModuleDescriptorImpl dependency : dependencies) { for (dependency in dependencies) {
if (dependency.getCapability(MultiTargetPlatform.CAPABILITY) == MultiTargetPlatform.Common.INSTANCE) { if (dependency.getCapability(MultiTargetPlatform.CAPABILITY) === MultiTargetPlatform.Common) {
List<KtFile> files = dependency.getCapability(MODULE_FILES); val files = dependency.getCapability(MODULE_FILES) ?: error("MODULE_FILES should have been set for the common module: " + dependency)
assert files != null : "MODULE_FILES should have been set for the common module: " + dependency; result.addAll(files)
result.addAll(files);
} }
} }
return result; return result
} }
private void validateAndCompareDescriptorWithFile( private fun validateAndCompareDescriptorWithFile(
File expectedFile, expectedFile: File,
List<TestFile> testFiles, testFiles: List<BaseDiagnosticsTest.TestFile>,
Map<TestModule, ModuleDescriptorImpl> modules modules: Map<BaseDiagnosticsTest.TestModule, ModuleDescriptorImpl>
) { ) {
if (CollectionsKt.any(testFiles, new Function1<TestFile, Boolean>() { if (testFiles.any { file -> InTextDirectivesUtils.isDirectiveDefined(file.expectedText, "// SKIP_TXT") }) {
@Override TestCase.assertFalse(".txt file should not exist if SKIP_TXT directive is used: " + expectedFile, expectedFile.exists())
public Boolean invoke(TestFile file) { return
return InTextDirectivesUtils.isDirectiveDefined(file.expectedText, "// SKIP_TXT");
}
})) {
TestCase.assertFalse(".txt file should not exist if SKIP_TXT directive is used: " + expectedFile, expectedFile.exists());
return;
} }
RecursiveDescriptorComparator comparator = new RecursiveDescriptorComparator(createdAffectedPackagesConfiguration(testFiles, modules.values())); val comparator = RecursiveDescriptorComparator(createdAffectedPackagesConfiguration(testFiles, modules.values))
boolean isMultiModuleTest = modules.size() != 1; val isMultiModuleTest = modules.size != 1
StringBuilder rootPackageText = new StringBuilder(); val rootPackageText = StringBuilder()
for (Iterator<TestModule> module = CollectionsKt.sorted(modules.keySet()).iterator(); module.hasNext(); ) { val module = modules.keys.sorted().iterator()
ModuleDescriptorImpl moduleDescriptor = modules.get(module.next()); while (module.hasNext()) {
PackageViewDescriptor aPackage = moduleDescriptor.getPackage(FqName.ROOT); val moduleDescriptor = modules[module.next()]
TestCase.assertFalse(aPackage.isEmpty()); val aPackage = moduleDescriptor.getPackage(FqName.ROOT)
TestCase.assertFalse(aPackage.isEmpty())
if (isMultiModuleTest) { if (isMultiModuleTest) {
rootPackageText.append(String.format("// -- Module: %s --\n", moduleDescriptor.getName())); rootPackageText.append(String.format("// -- Module: %s --\n", moduleDescriptor.getName()))
} }
String actualSerialized = comparator.serializeRecursively(aPackage); val actualSerialized = comparator.serializeRecursively(aPackage)
rootPackageText.append(actualSerialized); rootPackageText.append(actualSerialized)
if (isMultiModuleTest && module.hasNext()) { if (isMultiModuleTest && module.hasNext()) {
rootPackageText.append("\n\n"); rootPackageText.append("\n\n")
} }
} }
int lineCount = StringUtil.getLineBreakCount(rootPackageText); val lineCount = StringUtil.getLineBreakCount(rootPackageText)
assert lineCount < 1000 : assert(lineCount < 1000) {
"Rendered descriptors of this test take up " + lineCount + " lines. " + "Rendered descriptors of this test take up " + lineCount + " lines. " +
"Please ensure you don't render JRE contents to the .txt file. " + "Please ensure you don't render JRE contents to the .txt file. " +
"Such tests are hard to maintain, take long time to execute and are subject to sudden unreviewed changes anyway."; "Such tests are hard to maintain, take long time to execute and are subject to sudden unreviewed changes anyway."
KotlinTestUtils.assertEqualsToFile(expectedFile, rootPackageText.toString());
} }
private RecursiveDescriptorComparator.Configuration createdAffectedPackagesConfiguration(List<TestFile> testFiles, final Collection<? extends ModuleDescriptor> modules) { KotlinTestUtils.assertEqualsToFile(expectedFile, rootPackageText.toString())
final Set<Name> packagesNames = getTopLevelPackagesFromFileList(getJetFiles(testFiles, false));
Predicate<DeclarationDescriptor> stepIntoFilter = new Predicate<DeclarationDescriptor>() {
@Override
public boolean apply(DeclarationDescriptor descriptor) {
ModuleDescriptor module = DescriptorUtils.getContainingModuleOrNull(descriptor);
if (!modules.contains(module)) return false;
if (descriptor instanceof PackageViewDescriptor) {
FqName fqName = ((PackageViewDescriptor) descriptor).getFqName();
if (fqName.isRoot()) return true;
Name firstName = fqName.pathSegments().get(0);
return packagesNames.contains(firstName);
} }
return true; private fun createdAffectedPackagesConfiguration(testFiles: List<BaseDiagnosticsTest.TestFile>, modules: Collection<ModuleDescriptor>): RecursiveDescriptorComparator.Configuration {
} val packagesNames = getTopLevelPackagesFromFileList(getJetFiles(testFiles, false))
};
return RECURSIVE.filterRecursion(stepIntoFilter).withValidationStrategy(DescriptorValidator.ValidationVisitor.errorTypesAllowed()); val stepIntoFilter = Predicate<DeclarationDescriptor> { descriptor ->
val module = DescriptorUtils.getContainingModuleOrNull(descriptor!!)
if (!modules.contains(module)) return@Predicate false
if (descriptor is PackageViewDescriptor) {
val fqName = descriptor.fqName
if (fqName.isRoot) return@Predicate true
val firstName = fqName.pathSegments()[0]
return@Predicate packagesNames.contains(firstName)
} }
@NotNull true
private static Set<Name> getTopLevelPackagesFromFileList(@NotNull List<KtFile> files) {
Set<Name> shortNames = new LinkedHashSet<Name>();
for (KtFile file : files) {
List<Name> packageFqNameSegments = file.getPackageFqName().pathSegments();
Name name = packageFqNameSegments.isEmpty() ? SpecialNames.ROOT_PACKAGE : packageFqNameSegments.get(0);
shortNames.add(name);
}
return shortNames;
} }
private Map<TestModule, ModuleDescriptorImpl> createModules( return RECURSIVE.filterRecursion(stepIntoFilter).withValidationStrategy(DescriptorValidator.ValidationVisitor.errorTypesAllowed())
@NotNull Map<TestModule, List<TestFile>> groupedByModule,
@NotNull StorageManager storageManager
) {
Map<TestModule, ModuleDescriptorImpl> modules = new HashMap<TestModule, ModuleDescriptorImpl>();
for (TestModule testModule : groupedByModule.keySet()) {
ModuleDescriptorImpl module =
testModule == null ?
createSealedModule(storageManager) :
createModule(testModule.getName(), storageManager);
modules.put(testModule, module);
} }
for (TestModule testModule : groupedByModule.keySet()) { private fun getTopLevelPackagesFromFileList(files: List<KtFile>): Set<Name> {
if (testModule == null) continue; val shortNames = LinkedHashSet<Name>()
for (file in files) {
ModuleDescriptorImpl module = modules.get(testModule); val packageFqNameSegments = file.packageFqName.pathSegments()
List<ModuleDescriptorImpl> dependencies = new ArrayList<ModuleDescriptorImpl>(); val name = if (packageFqNameSegments.isEmpty()) SpecialNames.ROOT_PACKAGE else packageFqNameSegments[0]
dependencies.add(module); shortNames.add(name)
for (TestModule dependency : testModule.getDependencies()) { }
dependencies.add(modules.get(dependency)); return shortNames
} }
dependencies.add(module.getBuiltIns().getBuiltInsModule()); private fun createModules(
dependencies.addAll(getAdditionalDependencies(module)); groupedByModule: Map<BaseDiagnosticsTest.TestModule, List<BaseDiagnosticsTest.TestFile>>,
module.setDependencies(dependencies); storageManager: StorageManager
): Map<BaseDiagnosticsTest.TestModule, ModuleDescriptorImpl> {
val modules = HashMap<BaseDiagnosticsTest.TestModule, ModuleDescriptorImpl>()
for (testModule in groupedByModule.keys) {
val module = if (testModule == null)
createSealedModule(storageManager)
else
createModule(testModule.name, storageManager)
modules.put(testModule, module)
} }
return modules; for (testModule in groupedByModule.keys) {
if (testModule == null) continue
val module = modules[testModule]
val dependencies = ArrayList<ModuleDescriptorImpl>()
dependencies.add(module)
for (dependency in testModule.getDependencies()) {
dependencies.add(modules[dependency])
} }
@NotNull dependencies.add(module.builtIns.builtInsModule)
protected List<ModuleDescriptorImpl> getAdditionalDependencies(@NotNull ModuleDescriptorImpl module) { dependencies.addAll(getAdditionalDependencies(module))
return Collections.emptyList(); module.setDependencies(dependencies)
} }
@NotNull return modules
@SuppressWarnings("unchecked") }
protected ModuleDescriptorImpl createModule(@NotNull String moduleName, @NotNull StorageManager storageManager) {
String nameSuffix = StringsKt.substringAfterLast(moduleName, "-", ""); protected fun getAdditionalDependencies(module: ModuleDescriptorImpl): List<ModuleDescriptorImpl> {
MultiTargetPlatform platform = return emptyList()
nameSuffix.isEmpty() ? null : }
nameSuffix.equals("common") ? MultiTargetPlatform.Common.INSTANCE : new MultiTargetPlatform.Specific(nameSuffix);
Map capabilities = protected fun createModule(moduleName: String, storageManager: StorageManager): ModuleDescriptorImpl {
platform == null val nameSuffix = moduleName.substringAfterLast("-", "")
? Collections.emptyMap() val platform = if (nameSuffix.isEmpty())
: Collections.singletonMap(MultiTargetPlatform.CAPABILITY, platform); null
return new ModuleDescriptorImpl( else if (nameSuffix == "common") MultiTargetPlatform.Common else MultiTargetPlatform.Specific(nameSuffix)
Name.special("<" + moduleName + ">"), storageManager, new JvmBuiltIns(storageManager), val capabilities = if (platform == null)
platform == MultiTargetPlatform.Common.INSTANCE ? PlatformKind.DEFAULT : PlatformKind.JVM, emptyMap<Any, Any>()
else
Collections.singletonMap<ModuleDescriptor.Capability<MultiTargetPlatform>, MultiTargetPlatform>(MultiTargetPlatform.CAPABILITY, platform)
return ModuleDescriptorImpl(
Name.special("<$moduleName>"), storageManager, JvmBuiltIns(storageManager),
if (platform === MultiTargetPlatform.Common) PlatformKind.DEFAULT else PlatformKind.JVM,
SourceKind.TEST, capabilities SourceKind.TEST, capabilities
); )
} }
@NotNull protected fun createSealedModule(storageManager: StorageManager): ModuleDescriptorImpl {
protected ModuleDescriptorImpl createSealedModule(@NotNull StorageManager storageManager) { val moduleDescriptor = createModule("test-module", storageManager)
ModuleDescriptorImpl moduleDescriptor = createModule("test-module", storageManager); moduleDescriptor.setDependencies(moduleDescriptor, moduleDescriptor.builtIns.builtInsModule)
moduleDescriptor.setDependencies(moduleDescriptor, moduleDescriptor.getBuiltIns().getBuiltInsModule()); return moduleDescriptor
return moduleDescriptor;
} }
private static void checkAllResolvedCallsAreCompleted(@NotNull List<KtFile> jetFiles, @NotNull BindingContext bindingContext) { private fun checkAllResolvedCallsAreCompleted(jetFiles: List<KtFile>, bindingContext: BindingContext) {
for (KtFile file : jetFiles) { for (file in jetFiles) {
if (!AnalyzingUtils.getSyntaxErrorRanges(file).isEmpty()) { if (!AnalyzingUtils.getSyntaxErrorRanges(file).isEmpty()) {
return; return
} }
} }
ImmutableMap<Call, ResolvedCall<?>> resolvedCallsEntries = bindingContext.getSliceContents(BindingContext.RESOLVED_CALL); val resolvedCallsEntries = bindingContext.getSliceContents(BindingContext.RESOLVED_CALL)
for (Map.Entry<Call, ResolvedCall<?>> entry : resolvedCallsEntries.entrySet()) { for (entry in resolvedCallsEntries.entries) {
KtElement element = entry.getKey().getCallElement(); val element = entry.key.callElement
ResolvedCall<?> resolvedCall = entry.getValue(); val resolvedCall = entry.value
DiagnosticUtils.LineAndColumn lineAndColumn = val lineAndColumn = DiagnosticUtils.getLineAndColumnInPsiFile(element.containingFile, element.textRange)
DiagnosticUtils.getLineAndColumnInPsiFile(element.getContainingFile(), element.getTextRange());
TestCase.assertTrue("Resolved call for '" + element.getText() + "'" + lineAndColumn + " is not completed", TestCase.assertTrue("Resolved call for '" + element.text + "'" + lineAndColumn + " is not completed",
((MutableResolvedCall<?>) resolvedCall).isCompleted()); (resolvedCall as MutableResolvedCall<*>).isCompleted)
} }
checkResolvedCallsInDiagnostics(bindingContext); checkResolvedCallsInDiagnostics(bindingContext)
} }
@SuppressWarnings({"unchecked", "ConstantConditions"}) private fun checkResolvedCallsInDiagnostics(bindingContext: BindingContext) {
private static void checkResolvedCallsInDiagnostics(BindingContext bindingContext) { val diagnosticsStoringResolvedCalls1 = Sets.newHashSet<DiagnosticFactory1<PsiElement, Collection<ResolvedCall<*>>>>(
Set<DiagnosticFactory1<PsiElement, Collection<? extends ResolvedCall<?>>>> diagnosticsStoringResolvedCalls1 = Sets.newHashSet(
OVERLOAD_RESOLUTION_AMBIGUITY, NONE_APPLICABLE, CANNOT_COMPLETE_RESOLVE, UNRESOLVED_REFERENCE_WRONG_RECEIVER, OVERLOAD_RESOLUTION_AMBIGUITY, NONE_APPLICABLE, CANNOT_COMPLETE_RESOLVE, UNRESOLVED_REFERENCE_WRONG_RECEIVER,
ASSIGN_OPERATOR_AMBIGUITY, ITERATOR_AMBIGUITY); ASSIGN_OPERATOR_AMBIGUITY, ITERATOR_AMBIGUITY)
Set<DiagnosticFactory2<KtExpression, ? extends Comparable<? extends Comparable<?>>, Collection<? extends ResolvedCall<?>>>> val diagnosticsStoringResolvedCalls2 = Sets.newHashSet<DiagnosticFactory2<KtExpression, out Comparable<out Comparable<*>>, Collection<ResolvedCall<*>>>>(
diagnosticsStoringResolvedCalls2 = Sets.newHashSet( COMPONENT_FUNCTION_AMBIGUITY, DELEGATE_SPECIAL_FUNCTION_AMBIGUITY, DELEGATE_SPECIAL_FUNCTION_NONE_APPLICABLE)
COMPONENT_FUNCTION_AMBIGUITY, DELEGATE_SPECIAL_FUNCTION_AMBIGUITY, DELEGATE_SPECIAL_FUNCTION_NONE_APPLICABLE); val diagnostics = bindingContext.diagnostics
Diagnostics diagnostics = bindingContext.getDiagnostics(); for (diagnostic in diagnostics) {
for (Diagnostic diagnostic : diagnostics) { val factory = diagnostic.factory
DiagnosticFactory<?> factory = diagnostic.getFactory();
//noinspection SuspiciousMethodCalls
if (diagnosticsStoringResolvedCalls1.contains(factory)) { if (diagnosticsStoringResolvedCalls1.contains(factory)) {
assertResolvedCallsAreCompleted( assertResolvedCallsAreCompleted(
diagnostic, DiagnosticFactory.cast(diagnostic, diagnosticsStoringResolvedCalls1).getA()); diagnostic, DiagnosticFactory.cast(diagnostic, diagnosticsStoringResolvedCalls1).a)
} }
//noinspection SuspiciousMethodCalls
if (diagnosticsStoringResolvedCalls2.contains(factory)) { if (diagnosticsStoringResolvedCalls2.contains(factory)) {
assertResolvedCallsAreCompleted( assertResolvedCallsAreCompleted(
diagnostic, diagnostic,
DiagnosticFactory.cast(diagnostic, diagnosticsStoringResolvedCalls2).getB()); DiagnosticFactory.cast<DiagnosticWithParameters2<KtExpression, out Comparable<out Comparable<*>>, Collection<ResolvedCall<*>>>>(diagnostic, diagnosticsStoringResolvedCalls2).b)
} }
} }
} }
private static void assertResolvedCallsAreCompleted( private fun assertResolvedCallsAreCompleted(
@NotNull Diagnostic diagnostic, @NotNull Collection<? extends ResolvedCall<?>> resolvedCalls diagnostic: Diagnostic, resolvedCalls: Collection<ResolvedCall<*>>
) { ) {
boolean allCallsAreCompleted = true; var allCallsAreCompleted = true
for (ResolvedCall<?> resolvedCall : resolvedCalls) { for (resolvedCall in resolvedCalls) {
if (!((MutableResolvedCall<?>) resolvedCall).isCompleted()) { if (!(resolvedCall as MutableResolvedCall<*>).isCompleted) {
allCallsAreCompleted = false; allCallsAreCompleted = false
} }
} }
PsiElement element = diagnostic.getPsiElement(); val element = diagnostic.psiElement
DiagnosticUtils.LineAndColumn lineAndColumn = val lineAndColumn = DiagnosticUtils.getLineAndColumnInPsiFile(element.containingFile, element.textRange)
DiagnosticUtils.getLineAndColumnInPsiFile(element.getContainingFile(), element.getTextRange());
TestCase.assertTrue("Resolved calls stored in " + diagnostic.getFactory().getName() + "\n" + TestCase.assertTrue("Resolved calls stored in " + diagnostic.factory.name + "\n" +
"for '" + element.getText() + "'" + lineAndColumn + " are not completed", "for '" + element.text + "'" + lineAndColumn + " are not completed",
allCallsAreCompleted); allCallsAreCompleted)
}
companion object {
private val HASH_SANITIZER = fun(s: String): String {
return s.replace("@(\\d)+".toRegex(), "")
}
private val MODULE_FILES = ModuleDescriptor.Capability<List<KtFile>>("")
} }
} }
@@ -14,164 +14,339 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.checkers; package org.jetbrains.kotlin.checkers
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet
import com.google.common.collect.Lists; import com.google.common.collect.Lists
import com.intellij.lang.java.JavaLanguage; import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Condition
import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.Conditions
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory; import com.intellij.psi.PsiFileFactory
import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.Function; import com.intellij.util.Function
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil
import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashMap
import kotlin.collections.CollectionsKt; import org.jetbrains.kotlin.asJava.*
import kotlin.jvm.functions.Function1; import org.jetbrains.kotlin.config.*
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.asJava.DuplicateJvmSignatureUtilKt; import org.jetbrains.kotlin.load.java.InternalFlexibleTypeTransformer
import org.jetbrains.kotlin.config.*; import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.diagnostics.*; import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.load.java.InternalFlexibleTypeTransformer; import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.psi.KtDeclaration; import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.utils.*
import org.jetbrains.kotlin.resolve.BindingContext; import org.junit.Assert
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.utils.StringsKt;
import org.junit.Assert;
import java.io.File; import java.io.File
import java.util.*; import java.util.*
import java.util.regex.Matcher; import java.util.regex.Matcher
import java.util.regex.Pattern; import java.util.regex.Pattern
public abstract class BaseDiagnosticsTest abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava<BaseDiagnosticsTest.TestModule, BaseDiagnosticsTest.TestFile>() {
extends KotlinMultiFileTestWithJava<BaseDiagnosticsTest.TestModule, BaseDiagnosticsTest.TestFile> {
public static final String DIAGNOSTICS_DIRECTIVE = "DIAGNOSTICS"; override fun createTestModule(name: String): TestModule {
public static final Pattern DIAGNOSTICS_PATTERN = Pattern.compile("([\\+\\-!])(\\w+)\\s*"); return TestModule(name)
public static final ImmutableSet<DiagnosticFactory<?>> DIAGNOSTICS_TO_INCLUDE_ANYWAY = }
ImmutableSet.of(
override fun createTestFile(module: TestModule, fileName: String, text: String, directives: Map<String, String>): TestFile {
return TestFile(module, fileName, text, directives)
}
override fun doMultiFileTest(file: File, modules: Map<String, KotlinMultiFileTestWithJava<BaseDiagnosticsTest.TestFile, BaseDiagnosticsTest.TestModule>.ModuleAndDependencies>, testFiles: List<TestFile>) {
for (moduleAndDependencies in modules.values) {
val dependencies = moduleAndDependencies.dependencies.map(
{ name ->
val dependency = modules[name] ?: error("Dependency not found: " +
name +
" for module " +
moduleAndDependencies.module.name)
dependency.module
}
)
moduleAndDependencies.module.getDependencies().addAll(dependencies)
}
analyzeAndCheck(file, testFiles)
}
protected abstract fun analyzeAndCheck(
testDataFile: File,
files: List<TestFile>
)
protected fun getJetFiles(testFiles: List<TestFile>, includeExtras: Boolean): List<KtFile> {
var declareFlexibleType = false
var declareCheckType = false
val jetFiles = Lists.newArrayList<KtFile>()
for (testFile in testFiles) {
if (testFile.jetFile != null) {
jetFiles.add(testFile.jetFile)
}
declareFlexibleType = declareFlexibleType or testFile.declareFlexibleType
declareCheckType = declareCheckType or testFile.declareCheckType
}
if (includeExtras) {
if (declareFlexibleType) {
jetFiles.add(KotlinTestUtils.createFile("EXPLICIT_FLEXIBLE_TYPES.kt", EXPLICIT_FLEXIBLE_TYPES_DECLARATIONS, project))
}
if (declareCheckType) {
jetFiles.add(KotlinTestUtils.createFile("CHECK_TYPE.kt", CHECK_TYPE_DECLARATIONS, project))
}
}
return jetFiles
}
class TestModule(val name: String) : Comparable<TestModule> {
private val dependencies = ArrayList<TestModule>()
fun getDependencies(): MutableList<TestModule> {
return dependencies
}
override fun compareTo(module: TestModule): Int {
return name.compareTo(module.name)
}
override fun toString(): String {
return name
}
}
class DiagnosticTestLanguageVersionSettings(
private val languageFeatures: Map<LanguageFeature, Boolean>, override val apiVersion: ApiVersion
) : LanguageVersionSettings {
override fun supportsFeature(feature: LanguageFeature): Boolean {
val enabled = languageFeatures[feature]
return enabled ?: LanguageVersionSettingsImpl.DEFAULT.supportsFeature(feature)
}
override // TODO provide base language version
val languageVersion: LanguageVersion
get() = throw UnsupportedOperationException("This instance of LanguageVersionSettings should be used for tests only")
override fun equals(obj: Any?): Boolean {
return obj is DiagnosticTestLanguageVersionSettings &&
obj.languageFeatures == languageFeatures &&
obj.apiVersion == apiVersion
}
}
inner class TestFile(
val module: TestModule?,
fileName: String,
textWithMarkers: String,
directives: Map<String, String>
) {
private val diagnosedRanges = Lists.newArrayList<CheckerTestUtil.DiagnosedRange>()
val expectedText: String
private val clearText: String
val jetFile: KtFile?
private val whatDiagnosticsToConsider: Condition<Diagnostic>
val customLanguageVersionSettings: LanguageVersionSettings
private val declareCheckType: Boolean
private val declareFlexibleType: Boolean
val checkLazyLog: Boolean
private val markDynamicCalls: Boolean
val dynamicCallDescriptors: List<DeclarationDescriptor> = ArrayList()
init {
this.whatDiagnosticsToConsider = parseDiagnosticFilterDirective(directives)
this.customLanguageVersionSettings = parseLanguageVersionSettings(directives)
this.checkLazyLog = directives.containsKey(CHECK_LAZY_LOG_DIRECTIVE) || CHECK_LAZY_LOG_DEFAULT
this.declareCheckType = directives.containsKey(CHECK_TYPE_DIRECTIVE)
this.declareFlexibleType = directives.containsKey(EXPLICIT_FLEXIBLE_TYPES_DIRECTIVE)
this.markDynamicCalls = directives.containsKey(MARK_DYNAMIC_CALLS_DIRECTIVE)
if (fileName.endsWith(".java")) {
PsiFileFactory.getInstance(project).createFileFromText(fileName, JavaLanguage.INSTANCE, textWithMarkers)
// TODO: check there's not syntax errors
this.jetFile = null
this.clearText = textWithMarkers
this.expectedText = this.clearText
}
else {
this.expectedText = textWithMarkers
val textWithExtras = addExtras(expectedText)
this.clearText = CheckerTestUtil.parseDiagnosedRanges(textWithExtras, diagnosedRanges)
this.jetFile = TestCheckerUtil.createCheckAndReturnPsiFile(fileName, clearText, project)
for (diagnosedRange in diagnosedRanges) {
diagnosedRange.file = jetFile
}
}
}
private val imports: String
get() {
var imports = ""
if (declareCheckType) {
imports += CHECK_TYPE_IMPORT + "\n"
}
if (declareFlexibleType) {
imports += EXPLICIT_FLEXIBLE_TYPES_IMPORT + "\n"
}
return imports
}
private val extras: String
get() = "/*extras*/\n$imports/*extras*/\n\n"
private fun addExtras(text: String): String {
return addImports(text, extras)
}
private fun stripExtras(actualText: StringBuilder) {
val extras = extras
val start = actualText.indexOf(extras)
if (start >= 0) {
actualText.delete(start, start + extras.length)
}
}
private fun addImports(text: String, imports: String): String {
var text = text
val pattern = Pattern.compile("^package [\\.\\w\\d]*\n", Pattern.MULTILINE)
val matcher = pattern.matcher(text)
if (matcher.find()) {
// add imports after the package directive
text = text.substring(0, matcher.end()) + imports + text.substring(matcher.end())
}
else {
// add imports at the beginning
text = imports + text
}
return text
}
fun getActualText(bindingContext: BindingContext, actualText: StringBuilder, skipJvmSignatureDiagnostics: Boolean): Boolean {
if (this.jetFile == null) {
// TODO: check java files too
actualText.append(this.clearText)
return true
}
val jvmSignatureDiagnostics = if (skipJvmSignatureDiagnostics)
emptySet<Diagnostic>()
else
computeJvmSignatureDiagnostics(bindingContext)
val ok = booleanArrayOf(true)
val diagnostics = ContainerUtil.filter(
CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors(bindingContext, jetFile, markDynamicCalls, dynamicCallDescriptors).plus(
jvmSignatureDiagnostics),
whatDiagnosticsToConsider
)
val diagnosticToExpectedDiagnostic = ContainerUtil.newHashMap<Diagnostic, CheckerTestUtil.TextDiagnostic>()
CheckerTestUtil.diagnosticsDiff(diagnosticToExpectedDiagnostic, diagnosedRanges, diagnostics, object : CheckerTestUtil.DiagnosticDiffCallbacks {
override fun missingDiagnostic(diagnostic: CheckerTestUtil.TextDiagnostic, expectedStart: Int, expectedEnd: Int) {
val message = "Missing " + diagnostic.name + DiagnosticUtils.atLocation(jetFile, TextRange(expectedStart, expectedEnd))
System.err.println(message)
ok[0] = false
}
override fun wrongParametersDiagnostic(
expectedDiagnostic: CheckerTestUtil.TextDiagnostic,
actualDiagnostic: CheckerTestUtil.TextDiagnostic,
start: Int,
end: Int
) {
val message = "Parameters of diagnostic not equal at position "
+DiagnosticUtils.atLocation(jetFile, TextRange(start, end))
+". Expected: " + expectedDiagnostic.asString() + ", actual: " + actualDiagnostic.asString()
System.err.println(message)
ok[0] = false
}
override fun unexpectedDiagnostic(diagnostic: CheckerTestUtil.TextDiagnostic, actualStart: Int, actualEnd: Int) {
val message = "Unexpected " + diagnostic.name + DiagnosticUtils.atLocation(jetFile, TextRange(actualStart, actualEnd))
System.err.println(message)
ok[0] = false
}
})
actualText.append(CheckerTestUtil.addDiagnosticMarkersToText(jetFile, diagnostics, diagnosticToExpectedDiagnostic, Function<PsiFile, String> { file -> file.text }))
stripExtras(actualText)
return ok[0]
}
private fun computeJvmSignatureDiagnostics(bindingContext: BindingContext): Set<Diagnostic> {
val jvmSignatureDiagnostics = HashSet<Diagnostic>()
val declarations = PsiTreeUtil.findChildrenOfType(jetFile, KtDeclaration::class.java)
for (declaration in declarations) {
val diagnostics = getJvmSignatureDiagnostics(declaration, bindingContext.diagnostics,
GlobalSearchScope.allScope(project)) ?: continue
jvmSignatureDiagnostics.addAll(diagnostics.forElement(declaration))
}
return jvmSignatureDiagnostics
}
override fun toString(): String {
return jetFile!!.name
}
}
companion object {
val DIAGNOSTICS_DIRECTIVE = "DIAGNOSTICS"
val DIAGNOSTICS_PATTERN = Pattern.compile("([\\+\\-!])(\\w+)\\s*")
val DIAGNOSTICS_TO_INCLUDE_ANYWAY: ImmutableSet<DiagnosticFactory<*>> = ImmutableSet.of(
Errors.UNRESOLVED_REFERENCE, Errors.UNRESOLVED_REFERENCE,
Errors.UNRESOLVED_REFERENCE_WRONG_RECEIVER, Errors.UNRESOLVED_REFERENCE_WRONG_RECEIVER,
CheckerTestUtil.SyntaxErrorDiagnosticFactory.INSTANCE, CheckerTestUtil.SyntaxErrorDiagnosticFactory.INSTANCE,
CheckerTestUtil.DebugInfoDiagnosticFactory.ELEMENT_WITH_ERROR_TYPE, CheckerTestUtil.DebugInfoDiagnosticFactory.ELEMENT_WITH_ERROR_TYPE,
CheckerTestUtil.DebugInfoDiagnosticFactory.MISSING_UNRESOLVED, CheckerTestUtil.DebugInfoDiagnosticFactory.MISSING_UNRESOLVED,
CheckerTestUtil.DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET CheckerTestUtil.DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET
); )
public static final String LANGUAGE_DIRECTIVE = "LANGUAGE"; val LANGUAGE_DIRECTIVE = "LANGUAGE"
private static final Pattern LANGUAGE_PATTERN = Pattern.compile("([\\+\\-])(\\w+)\\s*"); private val LANGUAGE_PATTERN = Pattern.compile("([\\+\\-])(\\w+)\\s*")
public static final String API_VERSION_DIRECTIVE = "API_VERSION"; val API_VERSION_DIRECTIVE = "API_VERSION"
public static final String CHECK_TYPE_DIRECTIVE = "CHECK_TYPE"; val CHECK_TYPE_DIRECTIVE = "CHECK_TYPE"
public static final String CHECK_TYPE_PACKAGE = "tests._checkType"; val CHECK_TYPE_PACKAGE = "tests._checkType"
private static final String CHECK_TYPE_DECLARATIONS = "\npackage " + CHECK_TYPE_PACKAGE + private val CHECK_TYPE_DECLARATIONS = "\npackage " + CHECK_TYPE_PACKAGE +
"\nfun <T> checkSubtype(t: T) = t" + "\nfun <T> checkSubtype(t: T) = t" +
"\nclass Inv<T>" + "\nclass Inv<T>" +
"\nfun <E> Inv<E>._() {}" + "\nfun <E> Inv<E>._() {}" +
"\ninfix fun <T> T.checkType(f: Inv<T>.() -> Unit) {}"; "\ninfix fun <T> T.checkType(f: Inv<T>.() -> Unit) {}"
public static final String CHECK_TYPE_IMPORT = "import " + CHECK_TYPE_PACKAGE + ".*"; val CHECK_TYPE_IMPORT = "import $CHECK_TYPE_PACKAGE.*"
public static final String EXPLICIT_FLEXIBLE_TYPES_DIRECTIVE = "EXPLICIT_FLEXIBLE_TYPES"; val EXPLICIT_FLEXIBLE_TYPES_DIRECTIVE = "EXPLICIT_FLEXIBLE_TYPES"
public static final String EXPLICIT_FLEXIBLE_PACKAGE = InternalFlexibleTypeTransformer.FLEXIBLE_TYPE_CLASSIFIER.getPackageFqName().asString(); val EXPLICIT_FLEXIBLE_PACKAGE = InternalFlexibleTypeTransformer.FLEXIBLE_TYPE_CLASSIFIER.packageFqName.asString()
public static final String EXPLICIT_FLEXIBLE_CLASS_NAME = InternalFlexibleTypeTransformer.FLEXIBLE_TYPE_CLASSIFIER.getRelativeClassName().asString(); val EXPLICIT_FLEXIBLE_CLASS_NAME = InternalFlexibleTypeTransformer.FLEXIBLE_TYPE_CLASSIFIER.relativeClassName.asString()
private static final String EXPLICIT_FLEXIBLE_TYPES_DECLARATIONS private val EXPLICIT_FLEXIBLE_TYPES_DECLARATIONS = "\npackage " + EXPLICIT_FLEXIBLE_PACKAGE +
= "\npackage " + EXPLICIT_FLEXIBLE_PACKAGE + "\npublic class " + EXPLICIT_FLEXIBLE_CLASS_NAME + "<L, U>"
"\npublic class " + EXPLICIT_FLEXIBLE_CLASS_NAME + "<L, U>"; private val EXPLICIT_FLEXIBLE_TYPES_IMPORT = "import $EXPLICIT_FLEXIBLE_PACKAGE.$EXPLICIT_FLEXIBLE_CLASS_NAME"
private static final String EXPLICIT_FLEXIBLE_TYPES_IMPORT = "import " + EXPLICIT_FLEXIBLE_PACKAGE + "." + EXPLICIT_FLEXIBLE_CLASS_NAME; val CHECK_LAZY_LOG_DIRECTIVE = "CHECK_LAZY_LOG"
public static final String CHECK_LAZY_LOG_DIRECTIVE = "CHECK_LAZY_LOG"; val CHECK_LAZY_LOG_DEFAULT = "true" == System.getProperty("check.lazy.logs", "false")
public static final boolean CHECK_LAZY_LOG_DEFAULT = "true".equals(System.getProperty("check.lazy.logs", "false"));
public static final String MARK_DYNAMIC_CALLS_DIRECTIVE = "MARK_DYNAMIC_CALLS"; val MARK_DYNAMIC_CALLS_DIRECTIVE = "MARK_DYNAMIC_CALLS"
@Override private fun parseLanguageVersionSettings(directiveMap: Map<String, String>): LanguageVersionSettings? {
protected TestModule createTestModule(@NotNull String name) { val apiVersionString = directiveMap[API_VERSION_DIRECTIVE]
return new TestModule(name); val directives = directiveMap[LANGUAGE_DIRECTIVE]
if (apiVersionString == null && directives == null) return null
val apiVersion = (if (apiVersionString != null) ApiVersion.parse(apiVersionString) else ApiVersion.LATEST) ?: error("Unknown API version: " + apiVersionString!!)
val languageFeatures = if (directives == null) emptyMap<LanguageFeature, Boolean>() else collectLanguageFeatureMap(directives)
return DiagnosticTestLanguageVersionSettings(languageFeatures, apiVersion)
} }
@Override private fun collectLanguageFeatureMap(directives: String): Map<LanguageFeature, Boolean> {
protected TestFile createTestFile(TestModule module, String fileName, String text, Map<String, String> directives) { val matcher = LANGUAGE_PATTERN.matcher(directives)
return new TestFile(module, fileName, text, directives);
}
@Override
protected void doMultiFileTest(File file, final Map<String, ModuleAndDependencies> modules, List<TestFile> testFiles) {
for (final ModuleAndDependencies moduleAndDependencies : modules.values()) {
List<TestModule> dependencies = CollectionsKt.map(
moduleAndDependencies.dependencies,
new Function1<String, TestModule>() {
@Override
public TestModule invoke(String name) {
ModuleAndDependencies dependency = modules.get(name);
assert dependency != null : "Dependency not found: " +
name +
" for module " +
moduleAndDependencies.module.getName();
return dependency.module;
}
}
);
moduleAndDependencies.module.getDependencies().addAll(dependencies);
}
analyzeAndCheck(file, testFiles);
}
protected abstract void analyzeAndCheck(
File testDataFile,
List<TestFile> files
);
protected List<KtFile> getJetFiles(List<? extends TestFile> testFiles, boolean includeExtras) {
boolean declareFlexibleType = false;
boolean declareCheckType = false;
List<KtFile> jetFiles = Lists.newArrayList();
for (TestFile testFile : testFiles) {
if (testFile.getJetFile() != null) {
jetFiles.add(testFile.getJetFile());
}
declareFlexibleType |= testFile.declareFlexibleType;
declareCheckType |= testFile.declareCheckType;
}
if (includeExtras) {
if (declareFlexibleType) {
jetFiles.add(KotlinTestUtils.createFile("EXPLICIT_FLEXIBLE_TYPES.kt", EXPLICIT_FLEXIBLE_TYPES_DECLARATIONS, getProject()));
}
if (declareCheckType) {
jetFiles.add(KotlinTestUtils.createFile("CHECK_TYPE.kt", CHECK_TYPE_DECLARATIONS, getProject()));
}
}
return jetFiles;
}
@Nullable
private static LanguageVersionSettings parseLanguageVersionSettings(Map<String, String> directiveMap) {
String apiVersionString = directiveMap.get(API_VERSION_DIRECTIVE);
String directives = directiveMap.get(LANGUAGE_DIRECTIVE);
if (apiVersionString == null && directives == null) return null;
ApiVersion apiVersion = apiVersionString != null ? ApiVersion.Companion.parse(apiVersionString) : ApiVersion.LATEST;
assert apiVersion != null : "Unknown API version: " + apiVersionString;
Map<LanguageFeature, Boolean> languageFeatures =
directives == null ? Collections.<LanguageFeature, Boolean>emptyMap() : collectLanguageFeatureMap(directives);
return new DiagnosticTestLanguageVersionSettings(languageFeatures, apiVersion);
}
@NotNull
private static Map<LanguageFeature, Boolean> collectLanguageFeatureMap(@NotNull String directives) {
Matcher matcher = LANGUAGE_PATTERN.matcher(directives);
if (!matcher.find()) { if (!matcher.find()) {
Assert.fail( Assert.fail(
"Wrong syntax in the '// !" + LANGUAGE_DIRECTIVE + ": ...' directive:\n" + "Wrong syntax in the '// !" + LANGUAGE_DIRECTIVE + ": ...' directive:\n" +
@@ -179,46 +354,41 @@ public abstract class BaseDiagnosticsTest
"Must be '([+-]LanguageFeatureName)+'\n" + "Must be '([+-]LanguageFeatureName)+'\n" +
"where '+' means 'enable' and '-' means 'disable'\n" + "where '+' means 'enable' and '-' means 'disable'\n" +
"and language feature names are names of enum entries in LanguageFeature enum class" "and language feature names are names of enum entries in LanguageFeature enum class"
); )
} }
Map<LanguageFeature, Boolean> values = new HashMap<LanguageFeature, Boolean>(); val values = HashMap<LanguageFeature, Boolean>()
do { do {
boolean enable = matcher.group(1).equals("+"); val enable = matcher.group(1) == "+"
String name = matcher.group(2); val name = matcher.group(2)
LanguageFeature feature = LanguageFeature.fromString(name); val feature = LanguageFeature.fromString(name)
if (feature == null) { if (feature == null) {
Assert.fail( Assert.fail(
"Language feature not found, please check spelling: " + name + "\n" + "Language feature not found, please check spelling: " + name + "\n" +
"Known features:\n " + StringsKt.join(Arrays.asList(LanguageFeature.values()), "\n ") "Known features:\n " + join(Arrays.asList(*LanguageFeature.values()), "\n ")
); )
} }
if (values.put(feature, enable) != null) { if (values.put(feature, enable) != null) {
Assert.fail("Duplicate entry for the language feature: " + name); Assert.fail("Duplicate entry for the language feature: " + name)
} }
} }
while (matcher.find()); while (matcher.find())
return values; return values
} }
private static Condition<Diagnostic> parseDiagnosticFilterDirective(Map<String, String> directiveMap) { private fun parseDiagnosticFilterDirective(directiveMap: Map<String, String>): Condition<Diagnostic> {
String directives = directiveMap.get(DIAGNOSTICS_DIRECTIVE); val directives = directiveMap[DIAGNOSTICS_DIRECTIVE]
if (directives == null) { if (directives == null) {
// If "!API_VERSION" is present, disable the NEWER_VERSION_IN_SINCE_KOTLIN diagnostic. // If "!API_VERSION" is present, disable the NEWER_VERSION_IN_SINCE_KOTLIN diagnostic.
// Otherwise it would be reported in any non-trivial test on the @SinceKotlin value. // Otherwise it would be reported in any non-trivial test on the @SinceKotlin value.
if (directiveMap.containsKey(API_VERSION_DIRECTIVE)) { if (directiveMap.containsKey(API_VERSION_DIRECTIVE)) {
return new Condition<Diagnostic>() { return Condition { diagnostic -> diagnostic.factory !== Errors.NEWER_VERSION_IN_SINCE_KOTLIN }
@Override
public boolean value(Diagnostic diagnostic) {
return diagnostic.getFactory() != Errors.NEWER_VERSION_IN_SINCE_KOTLIN;
} }
}; return Conditions.alwaysTrue<Diagnostic>()
} }
return Conditions.alwaysTrue(); var condition = Conditions.alwaysTrue<Diagnostic>()
} val matcher = DIAGNOSTICS_PATTERN.matcher(directives)
Condition<Diagnostic> condition = Conditions.alwaysTrue();
Matcher matcher = DIAGNOSTICS_PATTERN.matcher(directives);
if (!matcher.find()) { if (!matcher.find()) {
Assert.fail("Wrong syntax in the '// !" + DIAGNOSTICS_DIRECTIVE + ": ...' directive:\n" + Assert.fail("Wrong syntax in the '// !" + DIAGNOSTICS_DIRECTIVE + ": ...' directive:\n" +
"found: '" + directives + "'\n" + "found: '" + directives + "'\n" +
@@ -226,303 +396,41 @@ public abstract class BaseDiagnosticsTest
"where '+' means 'include'\n" + "where '+' means 'include'\n" +
" '-' means 'exclude'\n" + " '-' means 'exclude'\n" +
" '!' means 'exclude everything but this'\n" + " '!' means 'exclude everything but this'\n" +
"directives are applied in the order of appearance, i.e. !FOO +BAR means include only FOO and BAR"); "directives are applied in the order of appearance, i.e. !FOO +BAR means include only FOO and BAR")
} }
boolean first = true; var first = true
do { do {
String operation = matcher.group(1); val operation = matcher.group(1)
final String name = matcher.group(2); val name = matcher.group(2)
Condition<Diagnostic> newCondition; var newCondition: Condition<Diagnostic>
if (ImmutableSet.of("ERROR", "WARNING", "INFO").contains(name)) { if (ImmutableSet.of("ERROR", "WARNING", "INFO").contains(name)) {
final Severity severity = Severity.valueOf(name); val severity = Severity.valueOf(name)
newCondition = new Condition<Diagnostic>() { newCondition = Condition<Diagnostic> { diagnostic -> diagnostic.severity == severity }
@Override
public boolean value(Diagnostic diagnostic) {
return diagnostic.getSeverity() == severity;
}
};
} }
else { else {
newCondition = new Condition<Diagnostic>() { newCondition = Condition<Diagnostic> { diagnostic -> name == diagnostic.factory.name }
@Override
public boolean value(Diagnostic diagnostic) {
return name.equals(diagnostic.getFactory().getName());
} }
}; if ("!" == operation) {
}
if ("!".equals(operation)) {
if (!first) { if (!first) {
Assert.fail("'" + operation + name + "' appears in a position rather than the first one, " + Assert.fail("'" + operation + name + "' appears in a position rather than the first one, " +
"which effectively cancels all the previous filters in this directive"); "which effectively cancels all the previous filters in this directive")
} }
condition = newCondition; condition = newCondition
} }
else if ("+".equals(operation)) { else if ("+" == operation) {
condition = Conditions.or(condition, newCondition); condition = Conditions.or(condition, newCondition)
} }
else if ("-".equals(operation)) { else if ("-" == operation) {
condition = Conditions.and(condition, Conditions.not(newCondition)); condition = Conditions.and(condition, Conditions.not(newCondition))
} }
first = false; first = false
} }
while (matcher.find()); while (matcher.find())
// We always include UNRESOLVED_REFERENCE and SYNTAX_ERROR because they are too likely to indicate erroneous test data // We always include UNRESOLVED_REFERENCE and SYNTAX_ERROR because they are too likely to indicate erroneous test data
return Conditions.or( return Conditions.or(
condition, condition,
new Condition<Diagnostic>() { Condition<Diagnostic> { diagnostic -> DIAGNOSTICS_TO_INCLUDE_ANYWAY.contains(diagnostic.factory) })
@Override
public boolean value(Diagnostic diagnostic) {
return DIAGNOSTICS_TO_INCLUDE_ANYWAY.contains(diagnostic.getFactory());
}
});
}
protected static class TestModule implements Comparable<TestModule> {
private final String name;
private final List<TestModule> dependencies = new ArrayList<TestModule>();
public TestModule(@NotNull String name) {
this.name = name;
}
@NotNull
public String getName() {
return name;
}
@NotNull
public List<TestModule> getDependencies() {
return dependencies;
}
@Override
public int compareTo(@NotNull TestModule module) {
return name.compareTo(module.getName());
}
@Override
public String toString() {
return getName();
}
}
public static class DiagnosticTestLanguageVersionSettings implements LanguageVersionSettings {
private final Map<LanguageFeature, Boolean> languageFeatures;
private final ApiVersion apiVersion;
public DiagnosticTestLanguageVersionSettings(
@NotNull Map<LanguageFeature, Boolean> languageFeatures, @NotNull ApiVersion apiVersion
) {
this.languageFeatures = languageFeatures;
this.apiVersion = apiVersion;
}
@Override
public boolean supportsFeature(@NotNull LanguageFeature feature) {
Boolean enabled = languageFeatures.get(feature);
return enabled != null ? enabled : LanguageVersionSettingsImpl.DEFAULT.supportsFeature(feature);
}
@NotNull
@Override
public LanguageVersion getLanguageVersion() {
// TODO provide base language version
throw new UnsupportedOperationException("This instance of LanguageVersionSettings should be used for tests only");
}
@NotNull
@Override
public ApiVersion getApiVersion() {
return apiVersion;
}
@Override
public boolean equals(Object obj) {
return obj instanceof DiagnosticTestLanguageVersionSettings &&
((DiagnosticTestLanguageVersionSettings) obj).languageFeatures.equals(languageFeatures) &&
((DiagnosticTestLanguageVersionSettings) obj).apiVersion.equals(apiVersion);
}
}
protected class TestFile {
private final List<CheckerTestUtil.DiagnosedRange> diagnosedRanges = Lists.newArrayList();
public final String expectedText;
private final TestModule module;
private final String clearText;
private final KtFile jetFile;
private final Condition<Diagnostic> whatDiagnosticsToConsider;
public final LanguageVersionSettings customLanguageVersionSettings;
private final boolean declareCheckType;
private final boolean declareFlexibleType;
public final boolean checkLazyLog;
private final boolean markDynamicCalls;
private final List<DeclarationDescriptor> dynamicCallDescriptors = new ArrayList<DeclarationDescriptor>();
public TestFile(
@Nullable TestModule module,
String fileName,
String textWithMarkers,
Map<String, String> directives
) {
this.module = module;
this.whatDiagnosticsToConsider = parseDiagnosticFilterDirective(directives);
this.customLanguageVersionSettings = parseLanguageVersionSettings(directives);
this.checkLazyLog = directives.containsKey(CHECK_LAZY_LOG_DIRECTIVE) || CHECK_LAZY_LOG_DEFAULT;
this.declareCheckType = directives.containsKey(CHECK_TYPE_DIRECTIVE);
this.declareFlexibleType = directives.containsKey(EXPLICIT_FLEXIBLE_TYPES_DIRECTIVE);
this.markDynamicCalls = directives.containsKey(MARK_DYNAMIC_CALLS_DIRECTIVE);
if (fileName.endsWith(".java")) {
PsiFileFactory.getInstance(getProject()).createFileFromText(fileName, JavaLanguage.INSTANCE, textWithMarkers);
// TODO: check there's not syntax errors
this.jetFile = null;
this.expectedText = this.clearText = textWithMarkers;
}
else {
this.expectedText = textWithMarkers;
String textWithExtras = addExtras(expectedText);
this.clearText = CheckerTestUtil.parseDiagnosedRanges(textWithExtras, diagnosedRanges);
this.jetFile = TestCheckerUtil.createCheckAndReturnPsiFile(fileName, clearText, getProject());
for (CheckerTestUtil.DiagnosedRange diagnosedRange : diagnosedRanges) {
diagnosedRange.setFile(jetFile);
}
}
}
@NotNull
private String getImports() {
String imports = "";
if (declareCheckType) {
imports += CHECK_TYPE_IMPORT + "\n";
}
if (declareFlexibleType) {
imports += EXPLICIT_FLEXIBLE_TYPES_IMPORT + "\n";
}
return imports;
}
private String getExtras() {
return "/*extras*/\n" + getImports() + "/*extras*/\n\n";
}
private String addExtras(String text) {
return addImports(text, getExtras());
}
private void stripExtras(StringBuilder actualText) {
String extras = getExtras();
int start = actualText.indexOf(extras);
if (start >= 0) {
actualText.delete(start, start + extras.length());
}
}
private String addImports(String text, String imports) {
Pattern pattern = Pattern.compile("^package [\\.\\w\\d]*\n", Pattern.MULTILINE);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
// add imports after the package directive
text = text.substring(0, matcher.end()) + imports + text.substring(matcher.end());
}
else {
// add imports at the beginning
text = imports + text;
}
return text;
}
@Nullable
public TestModule getModule() {
return module;
}
@Nullable
public KtFile getJetFile() {
return jetFile;
}
@NotNull
public List<DeclarationDescriptor> getDynamicCallDescriptors() {
return dynamicCallDescriptors;
}
public boolean getActualText(BindingContext bindingContext, StringBuilder actualText, boolean skipJvmSignatureDiagnostics) {
if (this.jetFile == null) {
// TODO: check java files too
actualText.append(this.clearText);
return true;
}
Set<Diagnostic> jvmSignatureDiagnostics = skipJvmSignatureDiagnostics
? Collections.<Diagnostic>emptySet()
: computeJvmSignatureDiagnostics(bindingContext);
final boolean[] ok = { true };
List<Diagnostic> diagnostics = ContainerUtil.filter(
CollectionsKt.plus(CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors(bindingContext, jetFile, markDynamicCalls, dynamicCallDescriptors),
jvmSignatureDiagnostics),
whatDiagnosticsToConsider
);
Map<Diagnostic, CheckerTestUtil.TextDiagnostic> diagnosticToExpectedDiagnostic = ContainerUtil.newHashMap();
CheckerTestUtil.diagnosticsDiff(diagnosticToExpectedDiagnostic, diagnosedRanges, diagnostics, new CheckerTestUtil.DiagnosticDiffCallbacks() {
@Override
public void missingDiagnostic(CheckerTestUtil.TextDiagnostic diagnostic, int expectedStart, int expectedEnd) {
String message = "Missing " + diagnostic.getName() + DiagnosticUtils.atLocation(jetFile, new TextRange(expectedStart, expectedEnd));
System.err.println(message);
ok[0] = false;
}
@Override
public void wrongParametersDiagnostic(
CheckerTestUtil.TextDiagnostic expectedDiagnostic,
CheckerTestUtil.TextDiagnostic actualDiagnostic,
int start,
int end
) {
String message = "Parameters of diagnostic not equal at position "
+ DiagnosticUtils.atLocation(jetFile, new TextRange(start, end))
+ ". Expected: " + expectedDiagnostic.asString() + ", actual: " + actualDiagnostic.asString();
System.err.println(message);
ok[0] = false;
}
@Override
public void unexpectedDiagnostic(CheckerTestUtil.TextDiagnostic diagnostic, int actualStart, int actualEnd) {
String message = "Unexpected " + diagnostic.getName() + DiagnosticUtils.atLocation(jetFile, new TextRange(actualStart, actualEnd));
System.err.println(message);
ok[0] = false;
}
});
actualText.append(CheckerTestUtil.addDiagnosticMarkersToText(jetFile, diagnostics, diagnosticToExpectedDiagnostic, new Function<PsiFile, String>() {
@Override
public String fun(PsiFile file) {
return file.getText();
}
}));
stripExtras(actualText);
return ok[0];
}
private Set<Diagnostic> computeJvmSignatureDiagnostics(BindingContext bindingContext) {
Set<Diagnostic> jvmSignatureDiagnostics = new HashSet<Diagnostic>();
Collection<KtDeclaration> declarations = PsiTreeUtil.findChildrenOfType(jetFile, KtDeclaration.class);
for (KtDeclaration declaration : declarations) {
Diagnostics diagnostics = DuplicateJvmSignatureUtilKt.getJvmSignatureDiagnostics(declaration, bindingContext.getDiagnostics(),
GlobalSearchScope.allScope(getProject()));
if (diagnostics == null) continue;
jvmSignatureDiagnostics.addAll(diagnostics.forElement(declaration));
}
return jvmSignatureDiagnostics;
}
@Override
public String toString() {
return jetFile.getName();
} }
} }
} }
@@ -14,134 +14,115 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.checkers; package org.jetbrains.kotlin.checkers
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CommonConfigurationKeys; import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.context.ModuleContext
import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.context.ModuleContext; import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS; import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult; import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JSConfigurationKeys; import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.config.LibrarySourcesConfig
import org.jetbrains.kotlin.js.config.LibrarySourcesConfig; import org.jetbrains.kotlin.js.resolve.*
import org.jetbrains.kotlin.js.resolve.BindingContextSlicesJsKt; import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.js.resolve.JsPlatform; import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor
import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor; import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.serialization.js.ModuleKind; import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.storage.StorageManager; import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.KotlinTestUtils;
import java.util.ArrayList; import java.util.ArrayList
import java.util.List;
import java.util.Map;
public abstract class AbstractDiagnosticsTestWithJsStdLib extends AbstractDiagnosticsTest { abstract class AbstractDiagnosticsTestWithJsStdLib : AbstractDiagnosticsTest() {
private JsConfig config; protected var config: JsConfig? = null
private set
@Override @Throws(Exception::class)
protected void setUp() throws Exception { override fun setUp() {
super.setUp(); super.setUp()
CompilerConfiguration configuration = getEnvironment().getConfiguration().copy(); val configuration = environment.configuration.copy()
configuration.put(CommonConfigurationKeys.MODULE_NAME, KotlinTestUtils.TEST_MODULE_NAME); configuration.put(CommonConfigurationKeys.MODULE_NAME, KotlinTestUtils.TEST_MODULE_NAME)
configuration.put(JSConfigurationKeys.LIBRARY_FILES, LibrarySourcesConfig.JS_STDLIB); configuration.put(JSConfigurationKeys.LIBRARY_FILES, LibrarySourcesConfig.JS_STDLIB)
config = new LibrarySourcesConfig(getProject(), configuration); config = LibrarySourcesConfig(project, configuration)
} }
@Override @Throws(Exception::class)
protected void tearDown() throws Exception { override fun tearDown() {
config = null; config = null
super.tearDown(); super.tearDown()
} }
@Override override fun getEnvironmentConfigFiles(): List<String> {
@NotNull return EnvironmentConfigFiles.JS_CONFIG_FILES
protected List<String> getEnvironmentConfigFiles() {
return EnvironmentConfigFiles.JS_CONFIG_FILES;
} }
@Override override fun analyzeModuleContents(
@NotNull moduleContext: ModuleContext,
protected JsAnalysisResult analyzeModuleContents( ktFiles: List<KtFile>,
@NotNull ModuleContext moduleContext, moduleTrace: BindingTrace,
@NotNull List<KtFile> ktFiles, languageVersionSettings: LanguageVersionSettings?,
@NotNull BindingTrace moduleTrace, separateModules: Boolean
@Nullable LanguageVersionSettings languageVersionSettings, ): JsAnalysisResult {
boolean separateModules
) {
// TODO: support LANGUAGE directive in JS diagnostic tests // TODO: support LANGUAGE directive in JS diagnostic tests
assert languageVersionSettings == null assert(languageVersionSettings == null) { BaseDiagnosticsTest.LANGUAGE_DIRECTIVE + " directive is not supported in JS diagnostic tests" }
: BaseDiagnosticsTest.LANGUAGE_DIRECTIVE + " directive is not supported in JS diagnostic tests"; moduleTrace.record<ModuleDescriptor, ModuleKind>(MODULE_KIND, moduleContext.module, getModuleKind(ktFiles))
moduleTrace.record(BindingContextSlicesJsKt.MODULE_KIND, moduleContext.getModule(), getModuleKind(ktFiles)); return TopDownAnalyzerFacadeForJS.analyzeFilesWithGivenTrace(ktFiles, moduleTrace, moduleContext, config!!)
return TopDownAnalyzerFacadeForJS.analyzeFilesWithGivenTrace(ktFiles, moduleTrace, moduleContext, config);
} }
@NotNull private fun getModuleKind(ktFiles: List<KtFile>): ModuleKind {
private static ModuleKind getModuleKind(@NotNull List<KtFile> ktFiles) { var kind = ModuleKind.PLAIN
ModuleKind kind = ModuleKind.PLAIN; for (file in ktFiles) {
for (KtFile file : ktFiles) { val text = file.text
String text = file.getText(); for (line in StringUtil.splitByLines(text)) {
for (String line : StringUtil.splitByLines(text)) { line = line.trim { it <= ' ' }
line = line.trim(); if (!line.startsWith("//")) continue
if (!line.startsWith("//")) continue; line = line.substring(2).trim { it <= ' ' }
line = line.substring(2).trim(); val parts = StringUtil.split(line, ":")
List<String> parts = StringUtil.split(line, ":"); if (parts.size != 2) continue
if (parts.size() != 2) continue;
if (!parts.get(0).trim().equals("MODULE_KIND")) continue; if (parts[0].trim { it <= ' ' } != "MODULE_KIND") continue
kind = ModuleKind.valueOf(parts.get(1).trim()); kind = ModuleKind.valueOf(parts[1].trim { it <= ' ' })
} }
} }
return kind; return kind
} }
@Override override fun getAdditionalDependencies(module: ModuleDescriptorImpl): List<ModuleDescriptorImpl> {
@NotNull val dependencies = ArrayList<ModuleDescriptorImpl>()
protected List<ModuleDescriptorImpl> getAdditionalDependencies(@NotNull ModuleDescriptorImpl module) { for (moduleDescriptor in config!!.moduleDescriptors) {
List<ModuleDescriptorImpl> dependencies = new ArrayList<ModuleDescriptorImpl>(); dependencies.add(moduleDescriptor.data)
for (JsModuleDescriptor<ModuleDescriptorImpl> moduleDescriptor : config.getModuleDescriptors()) {
dependencies.add(moduleDescriptor.getData());
} }
return dependencies; return dependencies
} }
@Override override fun shouldSkipJvmSignatureDiagnostics(groupedByModule: Map<BaseDiagnosticsTest.TestModule, List<BaseDiagnosticsTest.TestFile>>): Boolean {
public boolean shouldSkipJvmSignatureDiagnostics(Map<TestModule, List<TestFile>> groupedByModule) { return true
return true;
} }
@NotNull override fun createModule(moduleName: String, storageManager: StorageManager): ModuleDescriptorImpl {
@Override return ModuleDescriptorImpl(Name.special("<$moduleName>"), storageManager, JsPlatform.builtIns)
protected ModuleDescriptorImpl createModule(@NotNull String moduleName, @NotNull StorageManager storageManager) {
return new ModuleDescriptorImpl(Name.special("<" + moduleName + ">"), storageManager, JsPlatform.INSTANCE.getBuiltIns());
} }
@NotNull override fun createSealedModule(storageManager: StorageManager): ModuleDescriptorImpl {
@Override val module = createModule("kotlin-js-test-module", storageManager)
protected ModuleDescriptorImpl createSealedModule(@NotNull StorageManager storageManager) {
ModuleDescriptorImpl module = createModule("kotlin-js-test-module", storageManager);
List<ModuleDescriptorImpl> dependencies = new ArrayList<ModuleDescriptorImpl>(); val dependencies = ArrayList<ModuleDescriptorImpl>()
dependencies.add(module); dependencies.add(module)
dependencies.addAll(getAdditionalDependencies(module)); dependencies.addAll(getAdditionalDependencies(module))
dependencies.add(module.getBuiltIns().getBuiltInsModule()); dependencies.add(module.builtIns.builtInsModule)
module.setDependencies(dependencies); module.setDependencies(dependencies)
return module; return module
}
protected JsConfig getConfig() {
return config;
} }
} }