diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java index 03199499b0a..616d3c84e78 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java @@ -7,8 +7,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; -import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.resolve.calls.inference.SolutionStatus; +import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lexer.JetKeywordToken; import org.jetbrains.jet.resolve.DescriptorRenderer; @@ -43,6 +43,7 @@ public interface Errors { return e.getClass().getSimpleName() + ": " + e.getMessage(); } }; + UnresolvedReferenceDiagnosticFactory UNRESOLVED_REFERENCE = new UnresolvedReferenceDiagnosticFactory("Unresolved reference"); RedeclarationDiagnosticFactory REDECLARATION = RedeclarationDiagnosticFactory.REDECLARATION; diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/UnresolvedReferenceDiagnosticFactory.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/UnresolvedReferenceDiagnosticFactory.java index f5bbc087fd8..a3350a690ab 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/UnresolvedReferenceDiagnosticFactory.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/UnresolvedReferenceDiagnosticFactory.java @@ -2,15 +2,16 @@ package org.jetbrains.jet.lang.diagnostics; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.psi.JetReferenceExpression; +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; /** * @author abreslav */ -public class UnresolvedReferenceDiagnosticFactory extends AbstractDiagnosticFactory { -// public static final UnresolvedReferenceDiagnosticFactory INSTANCE = new UnresolvedReferenceDiagnosticFactory(); +public class UnresolvedReferenceDiagnosticFactory extends AbstractDiagnosticFactory + implements PsiElementOnlyDiagnosticFactory { private final String message; - + public UnresolvedReferenceDiagnosticFactory(String message) { this.message = message; } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java index 0c6113a8b03..3fa295944a8 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/BindingContext.java @@ -138,7 +138,7 @@ public interface BindingContext { WritableSlice LABEL_TARGET = Slices.sliceBuilder().build(); WritableSlice VALUE_PARAMETER_AS_PROPERTY = Slices.sliceBuilder().build(); - WritableSlice FQNAME_TO_CLASS_DESCRIPTOR = new BasicWritableSlice(DO_NOTHING); + WritableSlice FQNAME_TO_CLASS_DESCRIPTOR = new BasicWritableSlice(DO_NOTHING, true); WritableSlice FQNAME_TO_NAMESPACE_DESCRIPTOR = new BasicWritableSlice(DO_NOTHING); @SuppressWarnings("UnusedDeclaration") diff --git a/compiler/frontend/src/org/jetbrains/jet/util/slicedmap/BasicWritableSlice.java b/compiler/frontend/src/org/jetbrains/jet/util/slicedmap/BasicWritableSlice.java index 8ff50070242..952d1aab784 100644 --- a/compiler/frontend/src/org/jetbrains/jet/util/slicedmap/BasicWritableSlice.java +++ b/compiler/frontend/src/org/jetbrains/jet/util/slicedmap/BasicWritableSlice.java @@ -6,7 +6,7 @@ import java.lang.reflect.Modifier; /** * @author abreslav */ -public class BasicWritableSlice implements WritableSlice { +public class BasicWritableSlice implements WritableSlice { public static Void initSliceDebugNames(Class declarationOwner) { for (Field field : declarationOwner.getFields()) { @@ -26,9 +26,15 @@ public class BasicWritableSlice implements WritableSlice { private String debugName; private final RewritePolicy rewritePolicy; + private final boolean isCollective; public BasicWritableSlice(RewritePolicy rewritePolicy) { + this(rewritePolicy, false); + } + + public BasicWritableSlice(RewritePolicy rewritePolicy, boolean isCollective) { this.rewritePolicy = rewritePolicy; + this.isCollective = isCollective; } @Override @@ -62,7 +68,7 @@ public class BasicWritableSlice implements WritableSlice { @Override public boolean isCollective() { - return false; + return isCollective; } @Override diff --git a/compiler/frontend/src/org/jetbrains/jet/util/slicedmap/DelegatingSlice.java b/compiler/frontend/src/org/jetbrains/jet/util/slicedmap/DelegatingSlice.java index 8c3c6d641cd..3ca356956d1 100644 --- a/compiler/frontend/src/org/jetbrains/jet/util/slicedmap/DelegatingSlice.java +++ b/compiler/frontend/src/org/jetbrains/jet/util/slicedmap/DelegatingSlice.java @@ -3,6 +3,8 @@ package org.jetbrains.jet.util.slicedmap; import org.jetbrains.annotations.NotNull; /** + * Do nothing but dispatching all invokes to internal writable slice. + * * @author abreslav */ public class DelegatingSlice implements WritableSlice { @@ -17,10 +19,12 @@ public class DelegatingSlice implements WritableSlice { return delegate.isCollective(); } + @Override public boolean check(K key, V value) { return delegate.check(key, value); } + @Override public void afterPut(MutableSlicedMap map, K key, V value) { delegate.afterPut(map, key, value); } @@ -30,10 +34,12 @@ public class DelegatingSlice implements WritableSlice { return delegate.getRewritePolicy(); } + @Override public SlicedMapKey makeKey(K key) { return delegate.makeKey(key); } + @Override public V computeValue(SlicedMap map, K key, V value, boolean valueNotFound) { return delegate.computeValue(map, key, value, valueNotFound); } diff --git a/idea/src/org/jetbrains/jet/plugin/actions/JetAddImportAction.java b/idea/src/org/jetbrains/jet/plugin/actions/JetAddImportAction.java new file mode 100644 index 00000000000..5d5de0e61e6 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/actions/JetAddImportAction.java @@ -0,0 +1,154 @@ +package org.jetbrains.jet.plugin.actions; + +import com.google.common.collect.Lists; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.daemon.impl.actions.AddImportAction; +import com.intellij.codeInsight.hint.QuestionAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.plugin.quickfix.ImportClassHelper; + +import javax.swing.*; +import java.util.List; + +/** + * Automatically adds import directive to the file for resolving reference. + * Based on {@link AddImportAction} + * + * @author Nikolay Krasko + */ +public class JetAddImportAction implements QuestionAction { + + private final Project myProject; + private final Editor myEditor; + private final PsiElement myElement; + private final List possibleImports; + + /** + * @param project Project where action takes place. + * @param editor Editor where modification should be done. + * @param element Element with unresolved reference. + * @param imports Variants for resolution. + */ + public JetAddImportAction( + @NotNull Project project, + @NotNull Editor editor, + @NotNull PsiElement element, + @NotNull Iterable imports + ) { + myProject = project; + myEditor = editor; + myElement = element; + possibleImports = Lists.newArrayList(imports); + } + + @Override + public boolean execute() { + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + if (!myElement.isValid()){ + return false; + } + + // TODO: Validate resolution variants. See AddImportAction.execute() + + if (possibleImports.size() == 1){ + addImport(myElement, myProject, possibleImports.get(0)); + } + else{ + chooseClassAndImport(); + } + + return true; + } + + protected BaseListPopupStep getImportSelectionPopup() { + return new BaseListPopupStep(QuickFixBundle.message("class.to.import.chooser.title"), possibleImports) { + @Override + public boolean isAutoSelectionEnabled() { + return false; + } + + @Override + public PopupStep onChosen(String selectedValue, boolean finalChoice) { + if (selectedValue == null) { + return FINAL_CHOICE; + } + + if (finalChoice) { + addImport(myElement, myProject, selectedValue); + return FINAL_CHOICE; + } + + List toExclude = AddImportAction.getAllExcludableStrings(selectedValue); + + return new BaseListPopupStep(null, toExclude) { + @NotNull + @Override + public String getTextFor(String value) { + return "Exclude '" + value + "' from auto-import"; + } + + @Override + public PopupStep onChosen(String selectedValue, boolean finalChoice) { + if (finalChoice) { + AddImportAction.excludeFromImport(myProject, selectedValue); + } + + return super.onChosen(selectedValue, finalChoice); + } + }; + } + + @Override + public boolean hasSubstep(String selectedValue) { + return true; + } + + @NotNull + @Override + public String getTextFor(String value) { + return value; + } + + @Override + public Icon getIconFor(String aValue) { + return PlatformIcons.CLASS_ICON; + } + }; + } + + protected void addImport(final PsiElement element, final Project project, final String selectedImport) { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + // TODO: See {@link com.intellij.codeInsight.daemon.impl.actions.AddImportAction#_addImport} for more ideas. + // TODO: Optimize imports + ImportClassHelper.addImportDirective( + selectedImport, + ImportClassHelper.findOuterNamespace(element) + ); + } + }); + } + }, QuickFixBundle.message("add.import"), null); + } + + private void chooseClassAndImport() { + JBPopupFactory.getInstance().createListPopup(getImportSelectionPopup()).showInBestPositionFor(myEditor); + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/annotations/JetPsiChecker.java b/idea/src/org/jetbrains/jet/plugin/annotations/JetPsiChecker.java index cb7e36c4a98..85f52f2938b 100644 --- a/idea/src/org/jetbrains/jet/plugin/annotations/JetPsiChecker.java +++ b/idea/src/org/jetbrains/jet/plugin/annotations/JetPsiChecker.java @@ -9,12 +9,12 @@ import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.MultiRangeReference; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; @@ -22,7 +22,6 @@ import org.jetbrains.jet.lang.descriptors.VariableDescriptor; import org.jetbrains.jet.lang.diagnostics.*; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; -import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.plugin.JetHighlighter; import org.jetbrains.jet.plugin.compiler.WholeProjectAnalyzerFacade; @@ -53,7 +52,7 @@ public class JetPsiChecker implements Annotator { public void annotate(@NotNull PsiElement element, @NotNull final AnnotationHolder holder) { if (element instanceof JetFile) { JetFile file = (JetFile) element; - Project project = element.getProject(); + try { final BindingContext bindingContext = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(file); @@ -61,56 +60,11 @@ public class JetPsiChecker implements Annotator { Collection diagnostics = Sets.newLinkedHashSet(bindingContext.getDiagnostics()); Set redeclarations = Sets.newHashSet(); for (Diagnostic diagnostic : diagnostics) { - if (diagnostic.getFactory().getPsiFile(diagnostic) != file) continue; // This is needed because we have the same context for all files - Annotation annotation = null; - if (diagnostic.getSeverity() == Severity.ERROR) { - if (diagnostic instanceof UnresolvedReferenceDiagnostic) { - UnresolvedReferenceDiagnostic unresolvedReferenceDiagnostic = (UnresolvedReferenceDiagnostic) diagnostic; - JetReferenceExpression referenceExpression = unresolvedReferenceDiagnostic.getPsiElement(); - PsiReference reference = referenceExpression.getReference(); - if (reference instanceof MultiRangeReference) { - MultiRangeReference mrr = (MultiRangeReference) reference; - for (TextRange range : mrr.getRanges()) { - holder.createErrorAnnotation(range.shiftRight(referenceExpression.getTextOffset()), diagnostic.getMessage()).setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); - } - } - else { - holder.createErrorAnnotation(referenceExpression, diagnostic.getMessage()).setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); - } - } - else if (diagnostic instanceof RedeclarationDiagnostic) { - RedeclarationDiagnostic redeclarationDiagnostic = (RedeclarationDiagnostic) diagnostic; - annotation = markRedeclaration(redeclarations, redeclarationDiagnostic, holder); - } - else { - annotation = holder.createErrorAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic)); - } - } - else if (diagnostic.getSeverity() == Severity.WARNING) { - annotation = holder.createWarningAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic)); - if (diagnostic.getFactory() instanceof UnusedElementDiagnosticFactory) { - annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL); - } - } - if (annotation != null) { - if (diagnostic instanceof DiagnosticWithPsiElementImpl && diagnostic.getFactory() instanceof PsiElementOnlyDiagnosticFactory) { - PsiElementOnlyDiagnosticFactory factory = (PsiElementOnlyDiagnosticFactory) diagnostic.getFactory(); - Collection intentionActionFactories = QuickFixes.get(factory); - for (JetIntentionActionFactory intentionActionFactory : intentionActionFactories) { - IntentionAction action = null; - if (intentionActionFactory != null) { - action = intentionActionFactory.createAction((DiagnosticWithPsiElement) diagnostic); - } - if (action != null) { - annotation.registerFix(action); - } - } - } - Collection actions = QuickFixes.get(diagnostic.getFactory()); - for (IntentionAction action : actions) { - annotation.registerFix(action); - } - } + + // This is needed because we have the same context for all files + if (diagnostic.getFactory().getPsiFile(diagnostic) != file) continue; + + registerDiagnosticAnnotations(diagnostic, redeclarations, holder); } } @@ -118,7 +72,7 @@ public class JetPsiChecker implements Annotator { file.acceptChildren(new JetVisitorVoid() { @Override - public void visitSimpleNameExpression(JetSimpleNameExpression expression) { + public void visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression) { DeclarationDescriptor target = bindingContext.get(REFERENCE_TARGET, expression); if (target instanceof ValueParameterDescriptor) { ValueParameterDescriptor parameterDescriptor = (ValueParameterDescriptor) target; @@ -131,7 +85,7 @@ public class JetPsiChecker implements Annotator { super.visitSimpleNameExpression(expression); } - private void markVariableAsWrappedIfNeeded(ASTNode node, DeclarationDescriptor target) { + private void markVariableAsWrappedIfNeeded(@NotNull ASTNode node, DeclarationDescriptor target) { if (target instanceof VariableDescriptor) { VariableDescriptor variableDescriptor = (VariableDescriptor) target; if (bindingContext.get(MUST_BE_WRAPPED_IN_A_REF, variableDescriptor)) { @@ -142,7 +96,7 @@ public class JetPsiChecker implements Annotator { } @Override - public void visitProperty(JetProperty property) { + public void visitProperty(@NotNull JetProperty property) { DeclarationDescriptor declarationDescriptor = bindingContext.get(DECLARATION_TO_DESCRIPTOR, property); PsiElement nameIdentifier = property.getNameIdentifier(); if (nameIdentifier != null) { @@ -152,7 +106,7 @@ public class JetPsiChecker implements Annotator { } @Override - public void visitExpression(JetExpression expression) { + public void visitExpression(@NotNull JetExpression expression) { JetType autoCast = bindingContext.get(AUTOCAST, expression); if (autoCast != null) { holder.createInfoAnnotation(expression, "Automatically cast to " + autoCast).setTextAttributes(JetHighlighter.JET_AUTO_CAST_EXPRESSION); @@ -161,7 +115,7 @@ public class JetPsiChecker implements Annotator { } @Override - public void visitJetElement(JetElement element) { + public void visitJetElement(@NotNull JetElement element) { element.acceptChildren(this); } }); @@ -177,23 +131,111 @@ public class JetPsiChecker implements Annotator { } } - private String getMessage(Diagnostic diagnostic) { + private void registerDiagnosticAnnotations( + @NotNull Diagnostic diagnostic, + @NotNull Set redeclarations, + @NotNull final AnnotationHolder holder + ) { + if (diagnostic.getSeverity() == Severity.ERROR) { + if (diagnostic instanceof UnresolvedReferenceDiagnostic) { + UnresolvedReferenceDiagnostic unresolvedReferenceDiagnostic = (UnresolvedReferenceDiagnostic) diagnostic; + JetReferenceExpression referenceExpression = unresolvedReferenceDiagnostic.getPsiElement(); + PsiReference reference = referenceExpression.getReference(); + if (reference instanceof MultiRangeReference) { + MultiRangeReference mrr = (MultiRangeReference) reference; + for (TextRange range : mrr.getRanges()) { + Annotation annotation = holder.createErrorAnnotation( + range.shiftRight(referenceExpression.getTextOffset()), + diagnostic.getMessage()); + + registerQuickFix(annotation, diagnostic); + + annotation.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); + } + } + else { + Annotation annotation = holder.createErrorAnnotation(referenceExpression, diagnostic.getMessage()); + registerQuickFix(annotation, diagnostic); + annotation.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); + } + + return; + } + + if (diagnostic instanceof RedeclarationDiagnostic) { + RedeclarationDiagnostic redeclarationDiagnostic = (RedeclarationDiagnostic) diagnostic; + registerQuickFix(markRedeclaration(redeclarations, redeclarationDiagnostic, holder), diagnostic); + return; + } + + // General annotation + registerQuickFix( + holder.createErrorAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic)), + diagnostic); + } + else if (diagnostic.getSeverity() == Severity.WARNING) { + Annotation annotation = holder.createWarningAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic)); + registerQuickFix(annotation, diagnostic); + + if (diagnostic.getFactory() instanceof UnusedElementDiagnosticFactory) { + annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL); + } + } + } + + /* + * Add a quick fix if and return modified annotation. + */ + @Nullable + private Annotation registerQuickFix( + @Nullable Annotation annotation, + @NotNull Diagnostic diagnostic) { + + if (annotation == null) { + return null; + } + + if (diagnostic instanceof DiagnosticWithPsiElementImpl && diagnostic.getFactory() instanceof PsiElementOnlyDiagnosticFactory) { + PsiElementOnlyDiagnosticFactory factory = (PsiElementOnlyDiagnosticFactory) diagnostic.getFactory(); + Collection intentionActionFactories = QuickFixes.get(factory); + for (JetIntentionActionFactory intentionActionFactory : intentionActionFactories) { + IntentionAction action = null; + if (intentionActionFactory != null) { + action = intentionActionFactory.createAction((DiagnosticWithPsiElement) diagnostic); + } + if (action != null) { + annotation.registerFix(action); + } + } + } + + Collection actions = QuickFixes.get(diagnostic.getFactory()); + for (IntentionAction action : actions) { + annotation.registerFix(action); + } + + return annotation; + } + + @NotNull + private String getMessage(@NotNull Diagnostic diagnostic) { if (ApplicationManager.getApplication().isInternal() || ApplicationManager.getApplication().isUnitTestMode()) { return "[" + diagnostic.getFactory().getName() + "] " + diagnostic.getMessage(); } return diagnostic.getMessage(); } - private Annotation markRedeclaration(Set redeclarations, RedeclarationDiagnostic diagnostic, AnnotationHolder holder) { + @Nullable + private Annotation markRedeclaration(@NotNull Set redeclarations, @NotNull RedeclarationDiagnostic diagnostic, @NotNull AnnotationHolder holder) { if (!redeclarations.add(diagnostic.getPsiElement())) return null; return holder.createErrorAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic)); } - private void highlightBackingFields(final AnnotationHolder holder, JetFile file, final BindingContext bindingContext) { + private void highlightBackingFields(@NotNull final AnnotationHolder holder, @NotNull JetFile file, @NotNull final BindingContext bindingContext) { file.acceptChildren(new JetVisitorVoid() { @Override - public void visitProperty(JetProperty property) { + public void visitProperty(@NotNull JetProperty property) { VariableDescriptor propertyDescriptor = bindingContext.get(BindingContext.VARIABLE, property); if (propertyDescriptor instanceof PropertyDescriptor) { if (bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) propertyDescriptor)) { @@ -203,7 +245,7 @@ public class JetPsiChecker implements Annotator { } @Override - public void visitParameter(JetParameter parameter) { + public void visitParameter(@NotNull JetParameter parameter) { PropertyDescriptor propertyDescriptor = bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter); if (propertyDescriptor != null && bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)) { putBackingfieldAnnotation(holder, parameter); @@ -211,13 +253,13 @@ public class JetPsiChecker implements Annotator { } @Override - public void visitJetElement(JetElement element) { + public void visitJetElement(@NotNull JetElement element) { element.acceptChildren(this); } }); } - private void putBackingfieldAnnotation(AnnotationHolder holder, JetNamedDeclaration element) { + private void putBackingfieldAnnotation(@NotNull AnnotationHolder holder, @NotNull JetNamedDeclaration element) { PsiElement nameIdentifier = element.getNameIdentifier(); if (nameIdentifier != null) { holder.createInfoAnnotation( diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassFix.java new file mode 100644 index 00000000000..a1796a5a6d9 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassFix.java @@ -0,0 +1,190 @@ +package org.jetbrains.jet.plugin.quickfix; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.codeInsight.intention.HighPriorityAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.DelegatingGlobalSearchScope; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiShortNamesCache; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.diagnostics.DiagnosticWithPsiElement; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade; +import org.jetbrains.jet.plugin.JetFileType; +import org.jetbrains.jet.plugin.actions.JetAddImportAction; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Check possibility and perform fix for unresolved references. + * + * @author Nikolay Krasko + */ +public class ImportClassFix extends JetHintAction implements HighPriorityAction { + + @NotNull + private final List suggestions; + + public ImportClassFix(@NotNull JetSimpleNameExpression element) { + super(element); + suggestions = computeSuggestions(element); + } + + private static List computeSuggestions(@NotNull JetSimpleNameExpression element) { + final PsiFile file = element.getContainingFile(); + if (!(file instanceof JetFile)) { + return Collections.emptyList(); + } + + final BindingContext context = AnalyzerFacade.analyzeFileWithCache( + (JetFile) file, AnalyzerFacade.SINGLE_DECLARATION_PROVIDER); + + return getClassNames(element, context, file.getProject()); + } + + /* + * Searches for possible class names in kotlin context and java facade. + */ + public static List getClassNames(@NotNull JetSimpleNameExpression expression, @NotNull BindingContext context, @NotNull Project project) { + final String referenceName = expression.getReferencedName(); + + if (referenceName == null) { + return Collections.emptyList(); + } + + Set possibleResolveNames = Sets.newHashSet(); + possibleResolveNames.addAll(filterPossibleTypes(referenceName, context.getKeys(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR))); + possibleResolveNames.addAll(getJavaClasses(referenceName, project)); + + // TODO: Do appropriate sorting + return Lists.newArrayList(possibleResolveNames); + } + + private static Collection getJavaClasses(@NotNull final String typeName, @NotNull Project project) { + PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project); + + PsiClass[] classes = cache.getClassesByName(typeName, new DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) { + @Override + public boolean contains(@NotNull VirtualFile file) { + return myBaseScope.contains(file) && file.getFileType() != JetFileType.INSTANCE; + } + }); + + return Collections2.transform(Lists.newArrayList(classes), new Function() { + @Nullable + @Override + public String apply(@Nullable PsiClass javaClass) { + assert javaClass != null; + return javaClass.getQualifiedName(); + } + }); + } + + @NotNull + private static Collection filterPossibleTypes(@NotNull final String typeName, @NotNull Collection variants) { + return Collections2.filter(variants, new Predicate() { + @Override + public boolean apply(@Nullable String fqName) { + return fqName != null && (fqName.endsWith('.' + typeName) || fqName.equals(typeName)); + } + }); + } + + @Override + public boolean showHint(@NotNull Editor editor) { + if (suggestions.isEmpty()) { + return false; + } + + final Project project = editor.getProject(); + if (project == null) { + return false; + } + + String hintText = ShowAutoImportPass.getMessage(suggestions.size() > 1, suggestions.get(0)); + if (!ApplicationManager.getApplication().isUnitTestMode() && + !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint()) { + + HintManager.getInstance().showQuestionHint( + editor, hintText, + element.getTextOffset(), element.getTextRange().getEndOffset(), + createAction(project, editor)); + } + + return true; + } + + @Override + @NotNull + public String getText() { + return QuickFixBundle.message("import.class.fix"); + } + + @Override + @NotNull + public String getFamilyName() { + return QuickFixBundle.message("import.class.fix"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return super.isAvailable(project, editor, file) && !suggestions.isEmpty(); + } + + @Override + public void invoke(@NotNull final Project project, @NotNull final Editor editor, final PsiFile file) throws IncorrectOperationException { + CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { + @Override + public void run() { + createAction(project, editor).execute(); + } + }); + } + + @Override + public boolean startInWriteAction() { + return true; + } + + @NotNull + private JetAddImportAction createAction(@NotNull Project project, @NotNull Editor editor) { + return new JetAddImportAction(project, editor, element, suggestions); + } + + @Nullable + public static JetIntentionActionFactory createFactory() { + return new JetIntentionActionFactory() { + @Nullable + @Override + public JetIntentionAction createAction(@NotNull DiagnosticWithPsiElement diagnostic) { + assert diagnostic.getPsiElement() instanceof JetSimpleNameExpression; + + if (diagnostic.getPsiElement() instanceof JetSimpleNameExpression) { + JetSimpleNameExpression psiElement = (JetSimpleNameExpression) diagnostic.getPsiElement(); + return new ImportClassFix(psiElement); + } + + return null; + } + }; + }} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java index 5bc1c75db9b..7c8ddad2c99 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java @@ -3,7 +3,10 @@ package org.jetbrains.jet.plugin.quickfix; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.psi.JetDeclaration; +import org.jetbrains.jet.lang.psi.JetImportDirective; +import org.jetbrains.jet.lang.psi.JetNamespace; +import org.jetbrains.jet.lang.psi.JetPsiFactory; import org.jetbrains.jet.lang.types.ErrorUtils; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.plugin.JetPluginUtil; @@ -26,34 +29,50 @@ public class ImportClassHelper { perform(typeFullName, namespace, null, null); } - public static void perform(@NotNull String typeFullName, @NotNull PsiElement elementToReplace, @NotNull PsiElement newElement) { - PsiElement parent = elementToReplace; + /** + * Get the outer namespace PSI element for given element in the tree. + * + * @param element Some element in the tree. + * @return A namespace element in the tree. + */ + public static JetNamespace findOuterNamespace(@NotNull PsiElement element) { + PsiElement parent = element; while (!(parent instanceof JetNamespace)) { parent = parent.getParent(); assert parent != null; } - perform(typeFullName, (JetNamespace) parent, elementToReplace, newElement); + + return (JetNamespace) parent; } - public static void perform(@NotNull String typeFullName, @NotNull JetNamespace namespace, @Nullable PsiElement elementToReplace, @Nullable PsiElement newElement) { + public static void perform(@NotNull String typeFullName, @NotNull PsiElement elementToReplace, @NotNull PsiElement newElement) { + perform(typeFullName, findOuterNamespace(elementToReplace), elementToReplace, newElement); + } + + /** + * Add import directive into the PSI tree for the given namespace. + * + * @param importString full name of the import. Can contain .* if necessary. + * @param namespace Namespace where directive should be added. + */ + public static void addImportDirective(@NotNull String importString, @NotNull JetNamespace namespace) { List importDirectives = namespace.getImportDirectives(); - JetImportDirective newDirective = JetPsiFactory.createImportDirective(namespace.getProject(), typeFullName); + JetImportDirective newDirective = JetPsiFactory.createImportDirective(namespace.getProject(), importString); String lineSeparator = System.getProperty("line.separator"); if (!importDirectives.isEmpty()) { - boolean isPresent = false; + + // Check if import is already present for (JetImportDirective directive : importDirectives) { - if (directive.getText().endsWith(typeFullName) || - directive.getText().endsWith(typeFullName + ";")) { - isPresent = true; + if (directive.getText().endsWith(importString) || directive.getText().endsWith(importString + ";")) { + return; } } - if (!isPresent) { - JetImportDirective lastDirective = importDirectives.get(importDirectives.size() - 1); - lastDirective.getParent().addAfter(newDirective, lastDirective); - lastDirective.getParent().addAfter(JetPsiFactory.createWhiteSpace(namespace.getProject(), lineSeparator), lastDirective); - } + + JetImportDirective lastDirective = importDirectives.get(importDirectives.size() - 1); + lastDirective.getParent().addAfter(newDirective, lastDirective); + lastDirective.getParent().addAfter(JetPsiFactory.createWhiteSpace(namespace.getProject(), lineSeparator), lastDirective); } else { List declarations = namespace.getDeclarations(); @@ -62,6 +81,11 @@ public class ImportClassHelper { firstDeclaration.getParent().addBefore(newDirective, firstDeclaration); firstDeclaration.getParent().addBefore(JetPsiFactory.createWhiteSpace(namespace.getProject(), lineSeparator + lineSeparator), firstDeclaration); } + } + + public static void perform(@NotNull String typeFullName, @NotNull JetNamespace namespace, @Nullable PsiElement elementToReplace, @Nullable PsiElement newElement) { + addImportDirective(typeFullName, namespace); + if (elementToReplace != null && newElement != null && elementToReplace != newElement) { elementToReplace.replace(newElement); } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/JetHintAction.java b/idea/src/org/jetbrains/jet/plugin/quickfix/JetHintAction.java new file mode 100644 index 00000000000..2d2c953059f --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/JetHintAction.java @@ -0,0 +1,26 @@ +package org.jetbrains.jet.plugin.quickfix; + +import com.intellij.codeInspection.HintAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +/** + * A fix with the user information hint. + * + * @author Nikolay Krasko + */ +public abstract class JetHintAction extends JetIntentionAction implements HintAction { + + public JetHintAction(@NotNull T element) { + super(element); + } + + protected boolean isCaretNearRef(Editor editor, T ref) { + TextRange range = element.getTextRange(); + int offset = editor.getCaretModel().getOffset(); + + return offset == range.getEndOffset(); + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/JetIntentionAction.java b/idea/src/org/jetbrains/jet/plugin/quickfix/JetIntentionAction.java index df6670a19c9..70688c3ca5c 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/JetIntentionAction.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/JetIntentionAction.java @@ -24,7 +24,7 @@ public abstract class JetIntentionAction implements Intent @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return element.isValid(); + return element.isValid() && file.getManager().isInProject(file); } @Override diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index d34688b4736..7c1569b2534 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -42,7 +42,7 @@ public class QuickFixes { add(Errors.ABSTRACT_PROPERTY_IN_PRIMARY_CONSTRUCTOR_PARAMETERS, removeAbstractModifierFactory); add(Errors.ABSTRACT_PROPERTY_NOT_IN_CLASS, removeAbstractModifierFactory); - + JetIntentionActionFactory removePartsFromPropertyFactory = RemovePartsFromPropertyFix.createFactory(); add(Errors.ABSTRACT_PROPERTY_WITH_INITIALIZER, removeAbstractModifierFactory); add(Errors.ABSTRACT_PROPERTY_WITH_INITIALIZER, removePartsFromPropertyFactory); @@ -116,6 +116,9 @@ public class QuickFixes { add(Errors.INITIALIZATION_USING_BACKING_FIELD_CUSTOM_SETTER, changeToBackingFieldFactory); add(Errors.INITIALIZATION_USING_BACKING_FIELD_OPEN_SETTER, changeToBackingFieldFactory); + JetIntentionActionFactory unresolvedReferenceFactory = ImportClassFix.createFactory(); + add(Errors.UNRESOLVED_REFERENCE, unresolvedReferenceFactory); + ImplementMethodsHandler implementMethodsHandler = new ImplementMethodsHandler(); actionMap.put(Errors.ABSTRACT_MEMBER_NOT_IMPLEMENTED, implementMethodsHandler); actionMap.put(Errors.MANY_IMPL_MEMBER_NOT_IMPLEMENTED, implementMethodsHandler); diff --git a/idea/testData/quickfix/autoImports/afterImportSingleFile.kt b/idea/testData/quickfix/autoImports/afterImportSingleFile.kt new file mode 100644 index 00000000000..ad4e241e1f3 --- /dev/null +++ b/idea/testData/quickfix/autoImports/afterImportSingleFile.kt @@ -0,0 +1,12 @@ +// "Import Class" "true" +namespace a + +import a.b.M + +fun test() { + val v = M +} + +namespace b { + class M() { } +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/beforeImportSingleFile.kt b/idea/testData/quickfix/autoImports/beforeImportSingleFile.kt new file mode 100644 index 00000000000..06fcebf9b9a --- /dev/null +++ b/idea/testData/quickfix/autoImports/beforeImportSingleFile.kt @@ -0,0 +1,10 @@ +// "Import Class" "true" +namespace a + +fun test() { + val v = M +} + +namespace b { + class M() { } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/JetQuickFixTest.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/JetQuickFixTest.java index 3bfb5ac99d0..62faeb23d10 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/JetQuickFixTest.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/JetQuickFixTest.java @@ -19,7 +19,7 @@ import java.util.List; /** * @author svtk */ -public class JetQuickFixTest extends LightQuickFixTestCase { +public class JetQuickFixTest extends LightQuickFixTestCase { private final String dataPath; private final String name; private static FilenameFilter quickFixTestsFilter;