package com.haskforce.codeInsight;
import com.haskforce.psi.HaskellPsiUtil;
import com.haskforce.psi.impl.HaskellElementFactory;
import static com.haskforce.codeInsight.HaskellCompletionContributor.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.junit.Assert;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* Completion test driver. Add new completion test cases here.
*/
public class HaskellCompletionTest extends HaskellCompletionTestBase {
public void testKeywordImport() throws Throwable {
// Top level should work fine.
doTestInclude("<caret>", "import ");
// As well as after a module declaration.
doTestInclude(
"module Main where\n" +
"<caret>",
"import ");
// After multiple imports.
doTestInclude(
"import Foo\n" +
"import qualified Bar\n" +
"<caret>",
"import ");
// After an appropriate pragma.
doTestInclude(
"import Foo\n" +
"{-# ANN module \"foo\" #-}\n" +
"<caret>",
"import ");
// After a CPP.
doTestInclude(
"import Foo\n" +
"#if foo\n" +
"<caret>",
"import ");
// But not after we've started definitions.
doTestExclude(
"import Foo\n" +
"foo = 1\n" +
"<caret>",
"import ");
// Not even after a pragma.
doTestExclude(
"import Foo\n" +
"foo = 1\n" +
"{-# ANN module \"foo\" #-}\n" +
"<caret>",
"import ");
// Not in an expression.
doTestExclude(
"import Foo\n" +
"foo = <caret>",
"import ");
}
public void testKeywordQualified() throws Throwable {
doTestInclude("import <caret>", "qualified ");
}
public void testKeywordDo() throws Throwable {
doTestInclude(
"dayFractionToTimeOfDay = undefined\n" +
"bar = do<caret>",
"do", "dayFractionToTimeOfDay");
}
public void testPragmaTypes() throws Throwable {
doTestInclude("{-# <caret> #-}", getPragmaTypes());
}
public void testLanguages() throws Throwable {
final String[] fakeLangs = {"OverloadedStrings", "TypeFamilies", "OverloadedRecordFields"};
loadLanguageExtensions(fakeLangs);
doTestInclude("{-# LANGUAGE <caret> #-}", fakeLangs);
}
public void testGhcFlags() throws Throwable {
final String[] fakeFlags = {"-ferror-spans", "-fno-error-spans", "-fprint-explicit-foralls"};
loadGhcFlags(fakeFlags);
doTestInclude("{-# OPTIONS_GHC <caret> #-}", fakeFlags);
clearCache();
// Strip the first "-" from each flag.
String[] expected = new String[fakeFlags.length];
for (int i = 0; i < fakeFlags.length; ++i) {
expected[i] = fakeFlags[i].substring(1);
}
doTestInclude("{-# OPTIONS_GHC -<caret> #-}", expected);
}
public void testModuleImports() throws Throwable {
final String[] fakeModules = {"Control.Monad", "Data.ByteString", "Data.Byteable"};
loadVisibleModules(fakeModules);
doTestInclude("import <caret>", "Control", "Data");
doTestInclude("import Data.<caret>", "ByteString", "Byteable");
doTestExclude("import Data.<caret>", "import ");
}
public void testNameImports() throws Throwable {
FakeBrowseCache fakeBrowseCache = new FakeBrowseCacheBuilder()
.put("Data.Ord", "Down", "EQ", "GT", "LT", "Ord", "Ordering", "compare", "comparing")
.build();
loadModuleSymbols(fakeBrowseCache);
doTestInclude("import Data.Ord (<caret>)", fakeBrowseCache.get("Data.Ord"));
doTestInclude("import Data.Ord (c<caret>)", "compare", "comparing");
doTestInclude("import Data.Ord (Or<caret>)", "Ord", "Ordering");
doTestInclude("import Data.Ord as Foo (Or<caret>)", "Ord", "Ordering");
}
public void testImportParsing() throws Throwable {
PsiFile psiFile = HaskellElementFactory.createFileFromText(myFixture.getProject(),
"import qualified Data.ByteString.Char8 as C\n" +
"import qualified Control.Monad\n" +
"import Data.Maybe\n" +
"import Control.Applicative (liftA, liftA2, pure)\n" +
"import Prelude hiding (Maybe(Just, Nothing))\n" +
"import Control.Arrow ()\n" +
"import Data.Either as E");
List<HaskellPsiUtil.Import> actuals = HaskellPsiUtil.parseImports(psiFile);
List<HaskellPsiUtil.Import> expecteds = Arrays.asList(
HaskellPsiUtil.Import.qualifiedAs("Data.ByteString.Char8", "C", false, null),
HaskellPsiUtil.Import.qualified("Control.Monad", false, null),
HaskellPsiUtil.Import.global("Data.Maybe", false, null),
HaskellPsiUtil.Import.global("Control.Applicative", false, new String[]{"liftA", "liftA2", "pure"}),
HaskellPsiUtil.Import.global("Prelude", true, new String[]{"Maybe", "Just", "Nothing"}),
HaskellPsiUtil.Import.global("Control.Arrow", false, new String[]{}),
HaskellPsiUtil.Import.globalAs("Data.Either", "E", false, null));
Assert.assertArrayEquals(expecteds.toArray(), actuals.toArray());
}
public void testQualifiedNames() throws Throwable {
FakeBrowseCache fakeBrowseCache = new FakeBrowseCacheBuilder()
.put("Data.ByteString.Char8", "ByteString", "all", "any", "append", "appendFile", "break")
.put("C", "ByteString", "all", "any", "append", "appendFile", "break")
.build();
loadModuleSymbols(fakeBrowseCache);
doTestInclude(
"import qualified Data.ByteString.Char8 as C\n" +
"foo = C.<caret>",
fakeBrowseCache.get("Data.ByteString.Char8"));
doTestInclude(
"import qualified Data.ByteString.Char8\n" +
"foo = Data.ByteString.Char8.<caret>",
fakeBrowseCache.get("Data.ByteString.Char8"));
// We should not autocomplete browse cache in imports.
doTestExclude(
"import Data.ByteString.Char8.<caret>",
fakeBrowseCache.get("Data.ByteString.Char8"));
}
public void testLocalNames() throws Throwable {
FakeBrowseCache fakeBrowseCache = new FakeBrowseCacheBuilder()
.put("Prelude", "Just", "Nothing", "all", "any", "readFile")
.put("Control.Monad", "liftM", "mapM", "forM")
.put("C", "ByteString", "all", "any", "append", "appendFile", "break")
.build();
loadModuleSymbols(fakeBrowseCache);
doTestInclude(
"foo = <caret>",
fakeBrowseCache.get("Prelude"));
doTestInclude(
"import Control.Monad\n" +
"foo = <caret>",
fakeBrowseCache.get("Control.Monad"));
// Don't include Control.Monad completion if not imported.
doTestExclude(
"foo = <caret>",
fakeBrowseCache.get("Control.Monad"));
// Don't include all names when excluded from explicit import.
doTestExclude(
"import Control.Monad (liftM)\n" +
"foo = <caret>",
"mapM", "forM");
// Don't include names in the hiding clause.
doTestExclude(
"import Prelude hiding (Maybe(Just, Nothing), all)\n" +
"foo = <caret>",
"Just", "Nothing", "all");
}
public void testHiddenNames() throws Throwable {
FakeBrowseCache fakeBrowseCache = new FakeBrowseCacheBuilder()
.put("Control.Monad", "liftM", "mapM", "forM")
.build();
loadModuleSymbols(fakeBrowseCache);
String src =
"import Control.Monad hiding (liftM)\n" +
"main = <caret>";
doTestExclude(src, "liftM");
doTestInclude(src, "mapM", "forM");
}
public void testReferenceCompletion() throws Throwable {
// Complete basic references.
doTestInclude(
"foo :: String -> String\n" +
"foo = undefined\n" +
"bar = <caret>",
"foo");
// Complete multiple names from a multi-name reference.
doTestInclude(
"foo, bar :: String -> String\n" +
"foo = undefined\n" +
"bar = <caret>",
"foo", "bar");
// Don't complete references in the wrong context.
doTestExclude(
"import <caret>\n" +
"foo :: String -> String\n" +
"foo = undefined",
"foo");
doTestExclude(
"import qualified Control.Applicative as A\n" +
"foo :: String -> String\n" +
"foo = A.<caret>",
"foo");
}
private static class FakeBrowseCacheBuilder {
private final FakeBrowseCache underlying = new FakeBrowseCache();
FakeBrowseCacheBuilder put(String key, String... values) {
underlying.put(key, values);
return this;
}
public FakeBrowseCache build() {
return underlying;
}
}
private static class FakeBrowseCache extends HashMap<String, List<LookupElement>> {
private final HashMap<String, String[]> nameMap = new HashMap<>();
List<LookupElement> put(String key, String[] values) {
List<LookupElement> result = super.put(key, ContainerUtil.map(values, LookupElementUtil::fromString));
nameMap.put(key, values);
return result;
}
public String[] get(String key) {
return nameMap.get(key);
}
}
}