diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/DiagnosticUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/DiagnosticUtils.java index 07e72ee6000..ea3d2d57700 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/DiagnosticUtils.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/DiagnosticUtils.java @@ -4,7 +4,6 @@ import com.intellij.lang.ASTNode; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; @@ -42,8 +41,14 @@ public class DiagnosticUtils { return closestPsiElement.getContainingFile(); } + @NotNull public static String atLocation(@NotNull PsiFile file, @NotNull TextRange textRange) { - Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + Document document = file.getViewProvider().getDocument();//PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + return atLocation(file, textRange, document); + } + + @NotNull + public static String atLocation(PsiFile file, TextRange textRange, Document document) { int offset = textRange.getStartOffset(); VirtualFile virtualFile = file.getVirtualFile(); String pathSuffix = virtualFile == null ? "" : " in " + virtualFile.getPath(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolver.java index b03aad99a9e..2d239d6dbaf 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolver.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/CallResolver.java @@ -355,6 +355,7 @@ public class CallResolver { if (error) { failedCandidates.add(candidate); + checkTypesWithNoCallee(temporaryTrace, scope, task.getTypeArguments(), task.getValueArguments(), task.getFunctionLiteralArguments()); continue; } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 184822264e8..48949cbb3f0 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -19,6 +19,11 @@ + + + + diff --git a/idea/src/org/jetbrains/jet/checkers/CheckerTestUtil.java b/idea/src/org/jetbrains/jet/checkers/CheckerTestUtil.java new file mode 100644 index 00000000000..895019da95c --- /dev/null +++ b/idea/src/org/jetbrains/jet/checkers/CheckerTestUtil.java @@ -0,0 +1,302 @@ +package org.jetbrains.jet.checkers; + +import com.google.common.collect.*; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiFile; +import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.resolve.BindingContext; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author abreslav + */ +public class CheckerTestUtil { + public static final Comparator DIAGNOSTIC_COMPARATOR = new Comparator() { + @Override + public int compare(Diagnostic o1, Diagnostic o2) { + TextRange range1 = getTextRange(o1); + TextRange range2 = getTextRange(o2); + int startOffset1 = range1.getStartOffset(); + int startOffset2 = range2.getStartOffset(); + if (startOffset1 != startOffset2) { + // Start early -- go first + return startOffset1 - range2.getStartOffset(); + } + // start at the same offset, the one who end later is the outer, i.e. goes first + return range2.getEndOffset() - range1.getEndOffset(); + } + }; + private static final Pattern RANGE_START_OR_END_PATTERN = Pattern.compile("()|()"); + private static final Pattern INDIVIDUAL_DIAGNOSTIC_PATTERN = Pattern.compile("\\w+"); + + public interface DiagnosticDiffCallbacks { + void missingDiagnostic(String type, int expectedStart, int expectedEnd); + void unexpectedDiagnostic(String type, int actualStart, int actualEnd); + } + + public static void diagnosticsDiff(List expected, Collection actual, DiagnosticDiffCallbacks callbacks) { + Iterator expectedDiagnostics = expected.iterator(); + List sortedDiagnosticDescriptors = getSortedDiagnosticDescriptors(actual); + Iterator actualDiagnostics = sortedDiagnosticDescriptors.iterator(); + + DiagnosedRange currentExpected = safeAdvance(expectedDiagnostics); + DiagnosticDescriptor currentActual = safeAdvance(actualDiagnostics); + while (currentExpected != null || currentActual != null) { + if (currentExpected != null) { + if (currentActual == null) { + missingDiagnostics(callbacks, currentExpected); + currentExpected = safeAdvance(expectedDiagnostics); + } + else { + int expectedStart = currentExpected.getStart(); + int actualStart = currentActual.getStart(); + int expectedEnd = currentExpected.getEnd(); + int actualEnd = currentActual.getEnd(); + if (expectedStart < actualStart) { + missingDiagnostics(callbacks, currentExpected); + currentExpected = safeAdvance(expectedDiagnostics); + } + else if (expectedStart > actualStart) { + unexpectedDiagnostics(currentActual.getDiagnostics(), callbacks); + currentActual = safeAdvance(actualDiagnostics); + } + else if (expectedEnd > actualEnd) { + assert expectedStart == actualStart; + missingDiagnostics(callbacks, currentExpected); + currentExpected = safeAdvance(expectedDiagnostics); + } + else if (expectedEnd < actualEnd) { + assert expectedStart == actualStart; + unexpectedDiagnostics(currentActual.getDiagnostics(), callbacks); + currentActual = safeAdvance(actualDiagnostics); + } + else { + assert expectedStart == actualStart && expectedEnd == actualEnd; + Multiset actualDiagnosticTypes = currentActual.getDiagnosticTypeStrings(); + Multiset expectedDiagnosticTypes = currentExpected.getDiagnostics(); + if (!actualDiagnosticTypes.equals(expectedDiagnosticTypes)) { + Multiset expectedCopy = HashMultiset.create(expectedDiagnosticTypes); + expectedCopy.removeAll(actualDiagnosticTypes); + Multiset actualCopy = HashMultiset.create(actualDiagnosticTypes); + actualCopy.removeAll(expectedDiagnosticTypes); + + for (String type : expectedCopy) { + callbacks.missingDiagnostic(type, expectedStart, expectedEnd); + } + for (String type : actualCopy) { + callbacks.unexpectedDiagnostic(type, actualStart, actualEnd); + } + } + currentExpected = safeAdvance(expectedDiagnostics); + currentActual = safeAdvance(actualDiagnostics); + } + + } + } + else { + if (currentActual != null) { + unexpectedDiagnostics(currentActual.getDiagnostics(), callbacks); + currentActual = safeAdvance(actualDiagnostics); + } + else { + break; + } + } + +// assert expectedDiagnostics.hasNext() || actualDiagnostics.hasNext(); + } + } + + private static void unexpectedDiagnostics(List actual, DiagnosticDiffCallbacks callbacks) { + for (Diagnostic diagnostic : actual) { + TextRange textRange = getTextRange(diagnostic); + callbacks.unexpectedDiagnostic(diagnostic.getFactory().getName(), textRange.getStartOffset(), textRange.getEndOffset()); + } + } + + private static void missingDiagnostics(DiagnosticDiffCallbacks callbacks, DiagnosedRange currentExpected) { + for (String type : currentExpected.getDiagnostics()) { + callbacks.missingDiagnostic(type, currentExpected.getStart(), currentExpected.getEnd()); + } + } + + private static T safeAdvance(Iterator iterator) { + return iterator.hasNext() ? iterator.next() : null; + } + + public static String parseDiagnosedRanges(String text, List result) { + Matcher matcher = RANGE_START_OR_END_PATTERN.matcher(text); + + Stack opened = new Stack(); + + int offsetCompensation = 0; + + while (matcher.find()) { + int effectiveOffset = matcher.start() - offsetCompensation; + String matchedText = matcher.group(); + if ("".equals(matchedText)) { + opened.pop().setEnd(effectiveOffset); + } + else { + Matcher diagnosticTypeMatcher = INDIVIDUAL_DIAGNOSTIC_PATTERN.matcher(matchedText); + DiagnosedRange range = new DiagnosedRange(effectiveOffset); + while (diagnosticTypeMatcher.find()) { + range.addDiagnostic(diagnosticTypeMatcher.group()); + } + opened.push(range); + result.add(range); + } + offsetCompensation += matchedText.length(); + } + + matcher.reset(); + return matcher.replaceAll(""); + } + + public static StringBuffer addDiagnosticMarkersToText(PsiFile psiFile, BindingContext bindingContext) { + Collection diagnostics = bindingContext.getDiagnostics(); + + StringBuffer result = new StringBuffer(); + String text = psiFile.getText(); + if (!diagnostics.isEmpty()) { + List diagnosticDescriptors = getSortedDiagnosticDescriptors(diagnostics); + + Stack opened = new Stack(); + ListIterator iterator = diagnosticDescriptors.listIterator(); + DiagnosticDescriptor currentDescriptor = iterator.next(); + + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + while (!opened.isEmpty() && i == opened.peek().end) { + closeDiagnosticString(result); + opened.pop(); + } + if (currentDescriptor != null && i == currentDescriptor.start) { + openDiagnosticsString(result, currentDescriptor); + opened.push(currentDescriptor); + if (iterator.hasNext()) { + currentDescriptor = iterator.next(); + } + else { + currentDescriptor = null; + } + } + result.append(c); + } + } + else { + result.append(text); + } + return result; + } + + private static void openDiagnosticsString(StringBuffer result, DiagnosticDescriptor currentDescriptor) { + result.append(" iterator = currentDescriptor.diagnostics.iterator(); iterator.hasNext(); ) { + Diagnostic diagnostic = iterator.next(); + result.append(diagnostic.getFactory().getName()); + if (iterator.hasNext()) { + result.append(", "); + } + } + result.append("!>"); + } + + private static void closeDiagnosticString(StringBuffer result) { + result.append(""); + } + + private static List getSortedDiagnosticDescriptors(Collection diagnostics) { + List list = Lists.newArrayList(diagnostics); + Collections.sort(list, DIAGNOSTIC_COMPARATOR); + + List diagnosticDescriptors = Lists.newArrayList(); + DiagnosticDescriptor currentDiagnosticDescriptor = null; + for (Iterator iterator = list.iterator(); iterator.hasNext(); ) { + Diagnostic diagnostic = iterator.next(); + + TextRange textRange = getTextRange(diagnostic); + if (currentDiagnosticDescriptor != null && currentDiagnosticDescriptor.equalRange(textRange)) { + currentDiagnosticDescriptor.diagnostics.add(diagnostic); + } + else { + currentDiagnosticDescriptor = new DiagnosticDescriptor(textRange.getStartOffset(), textRange.getEndOffset(), diagnostic); + diagnosticDescriptors.add(currentDiagnosticDescriptor); + } + } + return diagnosticDescriptors; + } + + private static TextRange getTextRange(Diagnostic currentDiagnostic) { + return currentDiagnostic.getFactory().getTextRange(currentDiagnostic); + } + + private static class DiagnosticDescriptor { + private final int start; + private final int end; + private final List diagnostics = Lists.newArrayList(); + + DiagnosticDescriptor(int start, int end, Diagnostic diagnostic) { + this.start = start; + this.end = end; + this.diagnostics.add(diagnostic); + } + + public boolean equalRange(TextRange textRange) { + return start == textRange.getStartOffset() && end == textRange.getEndOffset(); + } + + public Multiset getDiagnosticTypeStrings() { + Multiset actualDiagnosticTypes = HashMultiset.create(); + for (Diagnostic diagnostic : diagnostics) { + actualDiagnosticTypes.add(diagnostic.getFactory().getName()); + } + return actualDiagnosticTypes; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + public List getDiagnostics() { + return diagnostics; + } + } + + public static class DiagnosedRange { + private final int start; + private int end; + private final Multiset diagnostics = HashMultiset.create(); + + private DiagnosedRange(int start) { + this.start = start; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + public Multiset getDiagnostics() { + return diagnostics; + } + + public void setEnd(int end) { + this.end = end; + } + + public void addDiagnostic(String diagnostic) { + diagnostics.add(diagnostic); + } + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/actions/CopyAsDiagnosticTestAction.java b/idea/src/org/jetbrains/jet/plugin/actions/CopyAsDiagnosticTestAction.java new file mode 100644 index 00000000000..b9c35a7986d --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/actions/CopyAsDiagnosticTestAction.java @@ -0,0 +1,50 @@ +package org.jetbrains.jet.plugin.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiFile; +import org.jetbrains.jet.checkers.CheckerTestUtil; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.plugin.AnalyzerFacade; + +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; + +/** + * @author abreslav + */ +public class CopyAsDiagnosticTestAction extends AnAction { + @Override + public void actionPerformed(AnActionEvent e) { + Editor editor = e.getData(PlatformDataKeys.EDITOR); + PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE); + assert editor != null && psiFile != null; + + BindingContext bindingContext = AnalyzerFacade.analyzeFileWithCache((JetFile) psiFile); + + String result = CheckerTestUtil.addDiagnosticMarkersToText(psiFile, bindingContext).toString(); + + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(result), new ClipboardOwner() { + @Override + public void lostOwnership(Clipboard clipboard, Transferable contents) {} + }); + } + + + @Override + public void update(AnActionEvent e) { + Editor editor = e.getData(PlatformDataKeys.EDITOR); + PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE); + e.getPresentation().setEnabled(editor != null && psiFile instanceof JetFile && ApplicationManager.getApplication().isInternal()); + } + +} diff --git a/idea/testData/checkerTestUtil/test.jet b/idea/testData/checkerTestUtil/test.jet new file mode 100644 index 00000000000..e78606b8885 --- /dev/null +++ b/idea/testData/checkerTestUtil/test.jet @@ -0,0 +1,15 @@ +fun foo(u : Unit) : Int = 1 + +fun test() : Int { + foo(1) + val a : fun() : Unit = { + foo(1) + } + return 1 - "1" +} + +class A() { + val x : Int = foo1(xx) +} + +fun foo1() {} \ No newline at end of file diff --git a/idea/testData/checkerWithErrorTypes/quick/Abstract.jet b/idea/testData/checkerWithErrorTypes/quick/Abstract.jet new file mode 100644 index 00000000000..e30dabbd6ab --- /dev/null +++ b/idea/testData/checkerWithErrorTypes/quick/Abstract.jet @@ -0,0 +1,241 @@ +namespace abstract + +class MyClass() { + //properties + val a: Int + val a1: Int = 1 + abstract val a2: Int + abstract val a3: Int = 1 + + var b: Int private set + var b1: Int = 0; private set + abstract var b2: Int private set + abstract var b3: Int = 0; private set + + var c: Int set(v: Int) { $c = v } + var c1: Int = 0; set(v: Int) { $c1 = v } + abstract var c2: Int set(v: Int) { $c2 = v } + abstract var c3: Int = 0; set(v: Int) { $c3 = v } + + val e: Int get() = a + val e1: Int = 0; get() = a + abstract val e2: Int get() = a + abstract val e3: Int = 0; get() = a + + //methods + fun f() + fun g() {} + abstract fun h() + abstract fun j() {} + + //property accessors + var i: Int abstract get abstract set + var i1: Int = 0; abstract get abstract set + + var j: Int get() = i; abstract set + var j1: Int = 0; get() = i; abstract set + + var k: Int abstract set + var k1: Int = 0; abstract set + + var l: Int abstract get abstract set + var l1: Int = 0; abstract get abstract set + + var n: Int abstract get abstract set(v: Int) {} +} + +abstract class MyAbstractClass() { + //properties + val a: Int + val a1: Int = 1 + abstract val a2: Int + abstract val a3: Int = 1 + + var b: Int private set + var b1: Int = 0; private set + abstract var b2: Int private set + abstract var b3: Int = 0; private set + + var c: Int set(v: Int) { $c = v } + var c1: Int = 0; set(v: Int) { $c1 = v } + abstract var c2: Int set(v: Int) { $c2 = v } + abstract var c3: Int = 0; set(v: Int) { $c3 = v } + + val e: Int get() = a + val e1: Int = 0; get() = a + abstract val e2: Int get() = a + abstract val e3: Int = 0; get() = a + + //methods + fun f() + fun g() {} + abstract fun h() + abstract fun j() {} + + //property accessors + var i: Int abstract get abstract set + var i1: Int = 0; abstract get abstract set + + var j: Int get() = i; abstract set + var j1: Int get() = i; abstract set + + var k: Int abstract set + var k1: Int = 0; abstract set + + var l: Int abstract get abstract set + var l1: Int = 0; abstract get abstract set + + var n: Int abstract get abstract set(v: Int) {} +} + +trait MyTrait { + //properties + val a: Int + val a1: Int = 1 + abstract val a2: Int + abstract val a3: Int = 1 + + var b: Int private set + var b1: Int = 0; private set + abstract var b2: Int private set + abstract var b3: Int = 0; private set + + var c: Int set(v: Int) { $c = v } + var c1: Int = 0; set(v: Int) { $c1 = v } + abstract var c2: Int set(v: Int) { $c2 = v } + abstract var c3: Int = 0; set(v: Int) { $c3 = v } + + val e: Int get() = a + val e1: Int = 0; get() = a + abstract val e2: Int get() = a + abstract val e3: Int = 0; get() = a + + //methods + fun f() + fun g() {} + abstract fun h() + abstract fun j() {} + + //property accessors + var i: Int abstract get abstract set + var i1: Int = 0; abstract get abstract set + + var j: Int get() = i; abstract set + var j1: Int = 0; get() = i; abstract set + + var k: Int abstract set + var k1: Int = 0; abstract set + + var l: Int abstract get abstract set + var l1: Int = 0; abstract get abstract set + + var n: Int abstract get abstract set(v: Int) {} +} + +enum class MyEnum() { + //properties + val a: Int + val a1: Int = 1 + abstract val a2: Int + abstract val a3: Int = 1 + + var b: Int private set + var b1: Int = 0; private set + abstract var b2: Int private set + abstract var b3: Int = 0; private set + + var c: Int set(v: Int) { $c = v } + var c1: Int = 0; set(v: Int) { $c1 = v } + abstract var c2: Int set(v: Int) { $c2 = v } + abstract var c3: Int = 0; set(v: Int) { $c3 = v } + + val e: Int get() = a + val e1: Int = 0; get() = a + abstract val e2: Int get() = a + abstract val e3: Int = 0; get() = a + + //methods + fun f() + fun g() {} + abstract fun h() + abstract fun j() {} + + //property accessors + var i: Int abstract get abstract set + var i1: Int = 0; abstract get abstract set + + var j: Int get() = i; abstract set + var j1: Int = 0; get() = i; abstract set + + var k: Int abstract set + var k1: Int = 0; abstract set + + var l: Int abstract get abstract set + var l1: Int = 0; abstract get abstract set + + var n: Int abstract get abstract set(v: Int) {} +} + +abstract enum class MyAbstractEnum() {} + +namespace MyNamespace { + //properties + val a: Int + val a1: Int = 1 + abstract val a2: Int + abstract val a3: Int = 1 + + var b: Int private set + var b1: Int = 0; private set + abstract var b2: Int private set + abstract var b3: Int = 0; private set + + var c: Int set(v: Int) { $c = v } + var c1: Int = 0; set(v: Int) { $c1 = v } + abstract var c2: Int set(v: Int) { $c2 = v } + abstract var c3: Int = 0; set(v: Int) { $c3 = v } + + val e: Int get() = a + val e1: Int = 0; get() = a + abstract val e2: Int get() = a + abstract val e3: Int = 0; get() = a + + //methods + fun f() + fun g() {} + abstract fun h() + abstract fun j() {} + + //property accessors + var i: Int abstract get abstract set + var i1: Int = 0; abstract get abstract set + + var j: Int get() = i; abstract set + var j1: Int = 0; get() = i; abstract set + + var k: Int abstract set + var k1: Int = 0; abstract set + + var l: Int abstract get abstract set + var l1: Int = 0; abstract get abstract set + + var n: Int abstract get abstract set(v: Int) {} +} + +//creating an instance +abstract class B1( + val i: Int, + val s: String +) { +} + +class B2() : B1(1, "r") {} + +abstract class B3(i: Int) { + this(): this(1) +} + +fun foo(a: B3) { + val a = B3() + val b = B1(2, "s") +} \ No newline at end of file diff --git a/idea/testData/checkerWithErrorTypes/quick/Basic.jet b/idea/testData/checkerWithErrorTypes/quick/Basic.jet new file mode 100644 index 00000000000..dea3b44a6e1 --- /dev/null +++ b/idea/testData/checkerWithErrorTypes/quick/Basic.jet @@ -0,0 +1,15 @@ +fun foo(u : Unit) : Int = 1 + +fun test() : Int { + foo(1) + val a : fun() : Unit = { + foo(1) + } + return 1 - "1" +} + +class A() { + val x : Int = foo1(xx) +} + +fun foo1() {} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/JetLiteFixture.java b/idea/tests/org/jetbrains/jet/JetLiteFixture.java new file mode 100644 index 00000000000..d3b1f6b3af3 --- /dev/null +++ b/idea/tests/org/jetbrains/jet/JetLiteFixture.java @@ -0,0 +1,189 @@ +package org.jetbrains.jet; + +import com.intellij.ide.startup.impl.StartupManagerImpl; +import com.intellij.lang.*; +import com.intellij.lang.impl.PsiBuilderFactoryImpl; +import com.intellij.mock.*; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; +import com.intellij.openapi.fileEditor.impl.LoadTextUtil; +import com.intellij.openapi.fileTypes.FileTypeFactory; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.options.SchemesManagerFactory; +import com.intellij.openapi.progress.impl.ProgressManagerImpl; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.psi.*; +import com.intellij.psi.impl.PsiCachedValuesFactory; +import com.intellij.psi.impl.PsiFileFactoryImpl; +import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry; +import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.testFramework.LightVirtualFile; +import com.intellij.testFramework.MockSchemesManagerFactory; +import com.intellij.testFramework.PlatformLiteFixture; +import com.intellij.testFramework.TestDataFile; +import com.intellij.util.CachedValuesManagerImpl; +import com.intellij.util.Function; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.MessageBusFactory; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.jet.lang.parsing.JetParserDefinition; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.PicoContainer; +import org.picocontainer.PicoInitializationException; +import org.picocontainer.PicoIntrospectionException; +import org.picocontainer.defaults.AbstractComponentAdapter; + +import java.io.File; +import java.io.IOException; + +/** + * @author abreslav + */ +public class JetLiteFixture extends PlatformLiteFixture { + protected String myFileExt; + @NonNls + protected final String myFullDataPath; + protected PsiFile myFile; + private MockPsiManager myPsiManager; + private PsiFileFactoryImpl myFileFactory; + protected Language myLanguage; + protected final ParserDefinition[] myDefinitions; + + public JetLiteFixture(@NonNls String dataPath) { + myFileExt = "jet"; + myFullDataPath = getTestDataPath() + "/" + dataPath; + myDefinitions = new ParserDefinition[] {new JetParserDefinition()}; + } + + protected String getTestDataPath() { + return JetTestCaseBase.getTestDataPathBase(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + initApplication(); + getApplication().getPicoContainer().registerComponent(new AbstractComponentAdapter("com.intellij.openapi.progress.ProgressManager", Object.class) { + @Override + public Object getComponentInstance(PicoContainer container) throws PicoInitializationException, PicoIntrospectionException { + return new ProgressManagerImpl(getApplication()); + } + + @Override + public void verify(PicoContainer container) throws PicoIntrospectionException { + } + }); + myProject = disposeOnTearDown(new MockProjectEx()); + myPsiManager = new MockPsiManager(myProject); + myFileFactory = new PsiFileFactoryImpl(myPsiManager); + final MutablePicoContainer appContainer = getApplication().getPicoContainer(); + registerComponentInstance(appContainer, MessageBus.class, MessageBusFactory.newMessageBus(getApplication())); + registerComponentInstance(appContainer, SchemesManagerFactory.class, new MockSchemesManagerFactory()); + final MockEditorFactory editorFactory = new MockEditorFactory(); + registerComponentInstance(appContainer, EditorFactory.class, editorFactory); + registerComponentInstance(appContainer, FileDocumentManager.class, new MockFileDocumentManagerImpl(new Function() { + @Override + public Document fun(CharSequence charSequence) { + return editorFactory.createDocument(charSequence); + } + }, FileDocumentManagerImpl.DOCUMENT_KEY)); + registerComponentInstance(appContainer, PsiDocumentManager.class, new MockPsiDocumentManager()); + myLanguage = myLanguage == null && myDefinitions != null && myDefinitions.length > 0 + ? myDefinitions[0].getFileNodeType().getLanguage() + : myLanguage; + registerComponentInstance(appContainer, FileTypeManager.class, new MockFileTypeManager(new MockLanguageFileType(myLanguage, myFileExt))); + registerApplicationService(PsiBuilderFactory.class, new PsiBuilderFactoryImpl()); + registerApplicationService(DefaultASTFactory.class, new DefaultASTFactoryImpl()); + registerApplicationService(ReferenceProvidersRegistry.class, new ReferenceProvidersRegistryImpl()); + myProject.registerService(CachedValuesManager.class, new CachedValuesManagerImpl(myProject, new PsiCachedValuesFactory(myPsiManager))); + myProject.registerService(PsiManager.class, myPsiManager); + myProject.registerService(StartupManager.class, new StartupManagerImpl(myProject)); + myProject.registerService(PsiFileFactory.class, new PsiFileFactoryImpl(myPsiManager)); + + registerExtensionPoint(FileTypeFactory.FILE_TYPE_FACTORY_EP, FileTypeFactory.class); + + for (ParserDefinition definition : myDefinitions) { + addExplicitExtension(LanguageParserDefinitions.INSTANCE, definition.getFileNodeType().getLanguage(), definition); + } + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + myFile = null; + myProject = null; + myPsiManager = null; + } + + protected String loadFile(@NonNls @TestDataFile String name) throws IOException { + return doLoadFile(myFullDataPath, name); + } + + private static String doLoadFile(String myFullDataPath, String name) throws IOException { + String fullName = myFullDataPath + File.separatorChar + name; + String text = FileUtil.loadFile(new File(fullName), CharsetToolkit.UTF8).trim(); + text = StringUtil.convertLineSeparators(text); + return text; + } + + protected PsiFile createPsiFile(String name, String text) { + return createFile(name + "." + myFileExt, text); + } + + protected PsiFile createFile(@NonNls String name, String text) { + LightVirtualFile virtualFile = new LightVirtualFile(name, myLanguage, text); + virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET); + return myFileFactory.trySetupPsiForFile(virtualFile, myLanguage, true, false); + } + + protected static void ensureParsed(PsiFile file) { + file.accept(new PsiElementVisitor() { + @Override + public void visitElement(PsiElement element) { + element.acceptChildren(this); + } + }); + } + + protected void registerApplicationService(final Class aClass, T object) { + getApplication().registerService(aClass, object); + Disposer.register(myProject, new Disposable() { + @Override + public void dispose() { + getApplication().getPicoContainer().unregisterComponent(aClass.getName()); + } + }); + } + + protected void addExplicitExtension(final LanguageExtension instance, final Language language, final T object) { + instance.addExplicitExtension(language, object); + Disposer.register(myProject, new Disposable() { + @Override + public void dispose() { + instance.removeExplicitExtension(language, object); + } + }); + } + + protected void prepareForTest(String name) throws IOException { + String text = loadFile(name + "." + myFileExt); + createAndCheckPsiFile(name, text); + } + + protected void createAndCheckPsiFile(String name, String text) { + myFile = createPsiFile(name, text); + ensureParsed(myFile); + assertEquals("light virtual file text mismatch", text, ((LightVirtualFile) myFile.getVirtualFile()).getContent().toString()); + assertEquals("virtual file text mismatch", text, LoadTextUtil.loadText(myFile.getVirtualFile())); + assertEquals("doc text mismatch", text, myFile.getViewProvider().getDocument().getText()); + assertEquals("psi text mismatch", text, myFile.getText()); + } +} diff --git a/idea/tests/org/jetbrains/jet/JetTestCaseBase.java b/idea/tests/org/jetbrains/jet/JetTestCaseBase.java index 5ae4d8ff51e..e13ee48f159 100644 --- a/idea/tests/org/jetbrains/jet/JetTestCaseBase.java +++ b/idea/tests/org/jetbrains/jet/JetTestCaseBase.java @@ -43,11 +43,11 @@ public abstract class JetTestCaseBase extends LightDaemonAnalyzerTestCase { return getTestDataPathBase(); } - protected static String getTestDataPathBase() { + public static String getTestDataPathBase() { return getHomeDirectory() + "/idea/testData"; } - private static String getHomeDirectory() { + public static String getHomeDirectory() { return new File(PathManager.getResourceRoot(JetTestCaseBase.class, "/org/jetbrains/jet/JetTestCaseBase.class")).getParentFile().getParentFile().getParent(); } diff --git a/idea/tests/org/jetbrains/jet/checkers/CheckerTestUtilTest.java b/idea/tests/org/jetbrains/jet/checkers/CheckerTestUtilTest.java new file mode 100644 index 00000000000..b2d27a6d52b --- /dev/null +++ b/idea/tests/org/jetbrains/jet/checkers/CheckerTestUtilTest.java @@ -0,0 +1,125 @@ +package org.jetbrains.jet.checkers; + +import com.google.common.collect.Lists; +import com.intellij.psi.PsiFile; +import org.jetbrains.jet.JetLiteFixture; +import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.resolve.AnalyzingUtils; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.ImportingStrategy; + +import java.util.Collections; +import java.util.List; + +/** + * @author abreslav + */ +public class CheckerTestUtilTest extends JetLiteFixture { + + public CheckerTestUtilTest() { + super("checkerTestUtil"); + } + + protected void doTest(TheTest theTest) throws Exception { + prepareForTest("test"); + theTest.test(myFile); + } + + public void testEquals() throws Exception { + doTest(new TheTest() { + @Override + protected void makeTestData(List diagnostics, List diagnosedRanges) { + } + }); + } + + public void testMissing() throws Exception { + doTest(new TheTest("Missing TYPE_MISMATCH at 56 to 57") { + @Override + protected void makeTestData(List diagnostics, List diagnosedRanges) { + diagnostics.remove(0); + } + }); + } + + public void testUnexpected() throws Exception { + doTest(new TheTest("Unexpected TYPE_MISMATCH at 56 to 57") { + @Override + protected void makeTestData(List diagnostics, List diagnosedRanges) { + diagnosedRanges.remove(0); + } + }); + } + + public void testBoth() throws Exception { + doTest(new TheTest("Unexpected TYPE_MISMATCH at 56 to 57", "Missing UNRESOLVED_REFERENCE at 166 to 168") { + @Override + protected void makeTestData(List diagnostics, List diagnosedRanges) { + diagnosedRanges.remove(0); + diagnostics.remove(diagnostics.size() - 1); + } + }); + } + + public void testMissingInTheMiddle() throws Exception { + doTest(new TheTest("Unexpected NONE_APPLICABLE at 122 to 123", "Missing TYPE_MISMATCH at 161 to 169") { + @Override + protected void makeTestData(List diagnostics, List diagnosedRanges) { + diagnosedRanges.remove(2); + diagnostics.remove(diagnostics.size() - 3); + } + }); + } + + private static abstract class TheTest { + private final String[] expected; + + protected TheTest(String... expectedMessages) { + this.expected = expectedMessages; + } + + public void test(PsiFile psiFile) { + BindingContext bindingContext = AnalyzingUtils.getInstance(ImportingStrategy.NONE).analyzeFileWithCache((JetFile) psiFile); + String expectedText = CheckerTestUtil.addDiagnosticMarkersToText(psiFile, bindingContext).toString(); + + List diagnosedRanges = Lists.newArrayList(); + CheckerTestUtil.parseDiagnosedRanges(expectedText, diagnosedRanges); + + List diagnostics = Lists.newArrayList(bindingContext.getDiagnostics()); + Collections.sort(diagnostics, CheckerTestUtil.DIAGNOSTIC_COMPARATOR); + + makeTestData(diagnostics, diagnosedRanges); + + final List expectedMessages = Lists.newArrayList(expected); + final List actualMessages = Lists.newArrayList(); + + CheckerTestUtil.diagnosticsDiff(diagnosedRanges, diagnostics, new CheckerTestUtil.DiagnosticDiffCallbacks() { + @Override + public void missingDiagnostic(String type, int expectedStart, int expectedEnd) { + String message = "Missing " + type + " at " + expectedStart + " to " + expectedEnd; + actualMessages.add(message); + } + + @Override + public void unexpectedDiagnostic(String type, int actualStart, int actualEnd) { + String message = "Unexpected " + type + " at " + actualStart + " to " + actualEnd; + actualMessages.add(message); + } + }); + + assertEquals(listToString(expectedMessages), listToString(actualMessages)); + } + + private String listToString(List expectedMessages) { + StringBuilder stringBuilder = new StringBuilder(); + for (String expectedMessage : expectedMessages) { + stringBuilder.append(expectedMessage).append("\n"); + } + return stringBuilder.toString(); + } + + protected abstract void makeTestData(List diagnostics, List diagnosedRanges); + } + +} diff --git a/idea/tests/org/jetbrains/jet/checkers/QuickJetPsiCheckerTest.java b/idea/tests/org/jetbrains/jet/checkers/QuickJetPsiCheckerTest.java new file mode 100644 index 00000000000..f9c0f0545c8 --- /dev/null +++ b/idea/tests/org/jetbrains/jet/checkers/QuickJetPsiCheckerTest.java @@ -0,0 +1,72 @@ +package org.jetbrains.jet.checkers; + +import com.google.common.collect.Lists; +import com.intellij.openapi.util.TextRange; +import junit.framework.Test; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.JetLiteFixture; +import org.jetbrains.jet.JetTestCaseBase; +import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.resolve.AnalyzingUtils; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.ImportingStrategy; + +import java.util.List; + +/** + * @author abreslav + */ +public class QuickJetPsiCheckerTest extends JetLiteFixture { + private String name; + + public QuickJetPsiCheckerTest(@NonNls String dataPath, String name) { + super(dataPath); + this.name = name; + } + + @Override + public String getName() { + return "test" + name; + } + + @Override + public void runTest() throws Exception { + String expectedText = loadFile(name + ".jet"); + List diagnosedRanges = Lists.newArrayList(); + String clearText = CheckerTestUtil.parseDiagnosedRanges(expectedText, diagnosedRanges); + + createAndCheckPsiFile(name, clearText); + JetFile jetFile = (JetFile) myFile; + BindingContext bindingContext = AnalyzingUtils.getInstance(ImportingStrategy.NONE).analyzeFileWithCache(jetFile); + + CheckerTestUtil.diagnosticsDiff(diagnosedRanges, bindingContext.getDiagnostics(), new CheckerTestUtil.DiagnosticDiffCallbacks() { + @Override + public void missingDiagnostic(String type, int expectedStart, int expectedEnd) { + String message = "Missing " + type + DiagnosticUtils.atLocation(myFile, new TextRange(expectedStart, expectedEnd)); + System.err.println(message); + } + + @Override + public void unexpectedDiagnostic(String type, int actualStart, int actualEnd) { + String message = "Unexpected " + type + DiagnosticUtils.atLocation(myFile, new TextRange(actualStart, actualEnd)); + System.err.println(message); + } + }); + + String actualText = CheckerTestUtil.addDiagnosticMarkersToText(jetFile, bindingContext).toString(); + + assertEquals(expectedText, actualText); + } + + public static Test suite() { + return JetTestCaseBase.suiteForDirectory(JetTestCaseBase.getTestDataPathBase(), "/checkerWithErrorTypes/quick", true, new JetTestCaseBase.NamedTestFactory() { + @NotNull + @Override + public Test createTest(@NotNull String dataPath, @NotNull String name) { + return new QuickJetPsiCheckerTest(dataPath, name); + } + }); + } +} diff --git a/idea/tests/org/jetbrains/jet/resolve/JetResolveTest.java b/idea/tests/org/jetbrains/jet/resolve/JetResolveTest.java index c5b220f1309..d3678955b79 100644 --- a/idea/tests/org/jetbrains/jet/resolve/JetResolveTest.java +++ b/idea/tests/org/jetbrains/jet/resolve/JetResolveTest.java @@ -143,42 +143,6 @@ public class JetResolveTest extends ExtensibleResolveTestCase { doTest(path, true, false); } -// public void testBasic() throws Exception { -// doTest("/resolve/Basic.jet", true, true); -// } -// -// public void testResolveToJava() throws Exception { -// doTest("/resolve/ResolveToJava.jet", true, true); -// } -// -// public void testResolveOfInfixExpressions() throws Exception { -// doTest("/resolve/ResolveOfInfixExpressions.jet", true, true); -// } -// -// public void testProjections() throws Exception { -// doTest("/resolve/Projections.jet", true, true); -// } -// -// public void testPrimaryConstructors() throws Exception { -// doTest("/resolve/PrimaryConstructors.jet", true, true); -// } -// -// public void testClassifiers() throws Exception { -// doTest("/resolve/Classifiers.jet", true, true); -// } -// -// public void testConstructorsAndInitializers() throws Exception { -// doTest("/resolve/ConstructorsAndInitializers.jet", true, true); -// } -// -// public void testNamespaces() throws Exception { -// doTest("/resolve/Namespaces.jet", true, true); -// } -// -// public void testTryCatch() throws Exception { -// doTest("/resolve/TryCatch.jet", true, true); -// } - public static Test suite() { return JetTestCaseBase.suiteForDirectory(getHomeDirectory() + "/idea/testData/", "/resolve/", true, new JetTestCaseBase.NamedTestFactory() { @NotNull