Merge remote-tracking branch 'origin/master'

This commit is contained in:
Konstantin Bulenkov
2012-09-20 18:53:32 +04:00
30 changed files with 807 additions and 200 deletions
@@ -20,6 +20,7 @@ import com.intellij.openapi.compiler.CompileScope;
import com.intellij.openapi.compiler.CompilerMessage;
import com.intellij.openapi.compiler.CompilerMessageCategory;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
@@ -30,6 +31,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.UUID;
/**
* @author Eugene Zhuravlev
@@ -37,6 +39,8 @@ import java.util.StringTokenizer;
*/
public abstract class ProblemsView {
private final Project myProject;
public static class SERVICE {
private SERVICE() {
}
@@ -46,29 +50,30 @@ public abstract class ProblemsView {
}
}
protected ProblemsView(Project project) {
myProject = project;
}
public abstract void clearMessages(CompileScope scope);
public abstract void clearMessages();
public abstract void addMessage(int type, @NotNull String[] text, @Nullable VirtualFile file, int line, int column, @Nullable Object data);
public abstract void addMessage(int type, @NotNull String[] text, @Nullable String groupName, @NotNull Navigatable navigatable, @Nullable String exportTextPrefix, @Nullable String rendererTextPrefix, @NotNull UUID sessionId);
public abstract void addMessage(int type, @NotNull String[] text, @Nullable VirtualFile underFileGroup, @Nullable VirtualFile file, int line, int column, @Nullable Object data);
public abstract void addMessage(int type, @NotNull String[] text, @Nullable String groupName, @NotNull Navigatable navigatable, @Nullable String exportTextPrefix, @Nullable String rendererTextPrefix, @Nullable Object data);
public void addMessage(CompilerMessage message) {
final Navigatable navigatable = message.getNavigatable();
public void addMessage(CompilerMessage message, @NotNull UUID sessionId) {
final VirtualFile file = message.getVirtualFile();
Navigatable navigatable = message.getNavigatable();
if (navigatable == null) {
if (file == null) {
return; // both navigatable and file must not be null
}
navigatable = new OpenFileDescriptor(myProject, file, -1, -1);
}
final CompilerMessageCategory category = message.getCategory();
final int type = CompilerTask.translateCategory(category);
final String[] text = convertMessage(message);
if (navigatable != null) {
final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText();
addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), message.getVirtualFile());
}
else {
addMessage(type, text, file, -1, -1, message.getVirtualFile());
}
final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText();
addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), sessionId);
}
public abstract void setProgress(String text, float fraction);
@@ -81,6 +81,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon
private final ProjectFileIndex myProjectFileIndex; // cached for performance reasons
private final ProjectCompileScope myProjectCompileScope;
private final long myStartCompilationStamp;
private final UUID mySessionId = UUID.randomUUID();
public CompileContextImpl(final Project project,
final CompilerTask compilerSession,
@@ -254,7 +255,7 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon
myTask.addMessage(msg);
}
if (myShouldUpdateProblemsView && msg.getCategory() == CompilerMessageCategory.ERROR) {
ProblemsViewImpl.SERVICE.getInstance(myProject).addMessage(msg);
ProblemsViewImpl.SERVICE.getInstance(myProject).addMessage(msg, mySessionId);
}
}
@@ -444,14 +444,14 @@ public class CompileDriver {
buildManager.cancelAutoMakeTasks(myProject);
return buildManager.scheduleBuild(myProject, compileContext.isRebuild(), compileContext.isMake(), scopes, paths, builderParams, new DefaultMessageHandler(myProject) {
@Override
public void buildStarted() {
public void buildStarted(UUID sessionId) {
final ProblemsView view = ProblemsViewImpl.SERVICE.getInstance(myProject);
view.clearMessages(compileContext.getCompileScope());
view.clearProgress();
}
@Override
public void sessionTerminated() {
public void sessionTerminated(UUID sessionId) {
}
@Override
@@ -466,7 +466,7 @@ public class CompileDriver {
}
@Override
protected void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) {
protected void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) {
final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind = message.getKind();
//System.out.println(compilerMessage.getText());
if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.PROGRESS) {
@@ -494,7 +494,7 @@ public class CompileDriver {
}
@Override
protected void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) {
protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) {
final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Type eventType = event.getEventType();
switch (eventType) {
case FILES_GENERATED:
@@ -20,7 +20,6 @@ import com.intellij.openapi.Disposable;
import com.intellij.openapi.compiler.CompileScope;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
@@ -31,6 +30,8 @@ import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* @author Eugene Zhuravlev
* Date: 9/18/12
@@ -41,6 +42,7 @@ public class ProblemsViewImpl extends ProblemsView{
private final ProblemsViewPanel myPanel;
public ProblemsViewImpl(final Project project, final ToolWindowManager wm) {
super(project);
myPanel = new ProblemsViewPanel(project);
Disposer.register(project, new Disposable() {
@Override
@@ -86,28 +88,13 @@ public class ProblemsViewImpl extends ProblemsView{
myPanel.clearMessages();
}
@Override
public void addMessage(final int type, @NotNull final String[] text, @Nullable final VirtualFile file, final int line, final int column, @Nullable final Object data) {
myPanel.addMessage(type, text, file, line, column, data);
}
@Override
public void addMessage(final int type,
@NotNull final String[] text,
@Nullable final VirtualFile underFileGroup,
@Nullable final VirtualFile file,
final int line,
final int column, @Nullable final Object data) {
myPanel.addMessage(type, text, underFileGroup, file, line, column, data);
}
@Override
public void addMessage(final int type,
@NotNull final String[] text,
@Nullable final String groupName,
@NotNull final Navigatable navigatable,
@Nullable final String exportTextPrefix, @Nullable final String rendererTextPrefix, @Nullable final Object data) {
myPanel.addMessage(type, text, groupName, navigatable, exportTextPrefix, rendererTextPrefix, data);
@Nullable final String exportTextPrefix, @Nullable final String rendererTextPrefix, @Nullable final UUID sessionId) {
myPanel.addMessage(type, text, groupName, navigatable, exportTextPrefix, rendererTextPrefix, sessionId);
}
@Override
@@ -53,14 +53,14 @@ class AutoMakeMessageHandler extends DefaultMessageHandler {
}
@Override
public void buildStarted() {
public void buildStarted(UUID sessionId) {
final ProblemsView view = ProblemsView.SERVICE.getInstance(myProject);
view.clearMessages();
view.clearProgress();
}
@Override
protected void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) {
protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) {
if (myProject.isDisposed()) {
return;
}
@@ -86,7 +86,7 @@ class AutoMakeMessageHandler extends DefaultMessageHandler {
}
@Override
protected void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) {
protected void handleCompileMessage(final UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) {
if (myProject.isDisposed()) {
return;
}
@@ -107,7 +107,7 @@ class AutoMakeMessageHandler extends DefaultMessageHandler {
final VirtualFile vFile = sourceFilePath != null? LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(sourceFilePath)) : null;
final long line = message.hasLine() ? message.getLine() : -1;
final long column = message.hasColumn() ? message.getColumn() : -1;
ProblemsView.SERVICE.getInstance(myProject).addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.ERROR, message.getText(), vFile, (int)line, (int)column, null));
ProblemsView.SERVICE.getInstance(myProject).addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.ERROR, message.getText(), vFile, (int)line, (int)column, null), sessionId);
}
}
@@ -117,7 +117,7 @@ class AutoMakeMessageHandler extends DefaultMessageHandler {
}
@Override
public void sessionTerminated() {
public void sessionTerminated(UUID sessionId) {
String statusMessage = null/*"Auto make completed"*/;
switch (myBuildStatus) {
case SUCCESS:
@@ -197,7 +197,7 @@ public class BuildManager implements ApplicationComponent{
return false;
}
}
// todo: probably we do not need this excessive filtering
for (Project project : activeProjects) {
if (!project.isInitialized() || ProjectRootManager.getInstance(project).getFileIndex().isInContent(eventFile)) {
return true;
@@ -451,7 +451,7 @@ public class BuildManager implements ApplicationComponent{
}
catch (Exception e) {
handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), null));
handler.sessionTerminated();
handler.sessionTerminated(sessionId);
return null;
}
}
@@ -469,7 +469,7 @@ public class BuildManager implements ApplicationComponent{
@Override
public void run() {
if (future.isCancelled() || project.isDisposed()) {
handler.sessionTerminated();
handler.sessionTerminated(sessionId);
future.setDone();
return;
}
@@ -516,7 +516,7 @@ public class BuildManager implements ApplicationComponent{
try {
if (project.isDisposed()) {
myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
handler.sessionTerminated();
handler.sessionTerminated(sessionId);
return;
}
myBuildsInProgress.put(projectPath, future);
@@ -533,7 +533,7 @@ public class BuildManager implements ApplicationComponent{
public void processTerminated(ProcessEvent event) {
final BuilderMessageHandler handler = myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
if (handler != null) {
handler.sessionTerminated();
handler.sessionTerminated(sessionId);
}
}
@@ -572,7 +572,7 @@ public class BuildManager implements ApplicationComponent{
catch (ExecutionException e) {
myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e));
handler.sessionTerminated();
handler.sessionTerminated(sessionId);
}
finally {
myBuildsInProgress.remove(projectPath);
@@ -584,7 +584,7 @@ public class BuildManager implements ApplicationComponent{
catch (Throwable e) {
myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e));
handler.sessionTerminated();
handler.sessionTerminated(sessionId);
future.setDone();
}
}
@@ -594,7 +594,7 @@ public class BuildManager implements ApplicationComponent{
}
catch (Throwable e) {
handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e));
handler.sessionTerminated();
handler.sessionTerminated(sessionId);
}
return null;
@@ -116,7 +116,7 @@ class BuildMessageDispatcher extends SimpleChannelHandler {
if (msgType == CmdlineRemoteProto.Message.BuilderMessage.Type.PARAM_REQUEST) {
final CmdlineRemoteProto.Message.ControllerMessage params = sessionData.params;
if (params != null) {
handler.buildStarted();
handler.buildStarted(sessionId);
sessionData.params = null;
Channels.write(ctx.getChannel(), CmdlineProtoUtil.toMessage(sessionId, params));
}
@@ -146,7 +146,7 @@ class BuildMessageDispatcher extends SimpleChannelHandler {
final BuilderMessageHandler handler = unregisterBuildMessageHandler(sessionData.sessionId);
if (handler != null) {
// notify the handler only if it has not been notified yet
handler.sessionTerminated();
handler.sessionTerminated(sessionData.sessionId);
}
}
}
@@ -25,11 +25,11 @@ import java.util.UUID;
* Date: 4/25/12
*/
public interface BuilderMessageHandler {
void buildStarted();
void buildStarted(UUID sessionId);
void handleBuildMessage(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage msg);
void handleFailure(UUID sessionId, CmdlineRemoteProto.Message.Failure failure);
void sessionTerminated();
void sessionTerminated(UUID sessionId);
}
@@ -63,17 +63,17 @@ public abstract class DefaultMessageHandler implements BuilderMessageHandler {
}
@Override
public void buildStarted() {
public void buildStarted(UUID sessionId) {
}
@Override
public final void handleBuildMessage(final Channel channel, final UUID sessionId, final CmdlineRemoteProto.Message.BuilderMessage msg) {
switch (msg.getType()) {
case BUILD_EVENT:
handleBuildEvent(msg.getBuildEvent());
handleBuildEvent(sessionId, msg.getBuildEvent());
break;
case COMPILE_MESSAGE:
handleCompileMessage(msg.getCompileMessage());
handleCompileMessage(sessionId, msg.getCompileMessage());
break;
case CONSTANT_SEARCH_TASK:
final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task = msg.getConstantSearchTask();
@@ -87,9 +87,9 @@ public abstract class DefaultMessageHandler implements BuilderMessageHandler {
}
}
protected abstract void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message);
protected abstract void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message);
protected abstract void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event);
protected abstract void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event);
private void handleConstantSearchTask(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) {
final String ownerClassName = task.getOwnerClassName();
@@ -375,6 +375,7 @@ public class JavaMemberNameCompletionContributor extends CompletionContributor {
for (final PsiField field : fields) {
if (field == element) continue;
assert field.isValid();
final PsiModifierList modifierList = field.getModifierList();
if (staticContext && (modifierList != null && !modifierList.hasModifierProperty(PsiModifier.STATIC))) continue;
@@ -220,7 +220,7 @@ public class JavaQualifiedNameProvider implements QualifiedNameProvider {
catch (IncorrectOperationException e) {
// failed to bind
}
if (!isReferencedTo(referenceExpression, targetElement)) {
if (referenceExpression.isValid() && !isReferencedTo(referenceExpression, targetElement)) {
toInsert = fqn;
}
}
@@ -34,6 +34,7 @@ import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
@@ -391,6 +392,17 @@ public class JavaDocumentationProvider implements CodeDocumentationProvider, Ext
return null;
}
@Nullable
@Override
public Pair<PsiElement, PsiComment> parseContext(@NotNull PsiElement startPoint) {
for (PsiElement e = startPoint; e != null; e = e.getParent()) {
if (e instanceof PsiDocCommentOwner) {
return Pair.<PsiElement, PsiComment>create(e, ((PsiDocCommentOwner)e).getDocComment());
}
}
return null;
}
@Override
public String generateDocumentationContentStub(PsiComment _comment) {
final PsiDocCommentOwner commentOwner = ((PsiDocComment)_comment).getOwner();
@@ -93,7 +93,11 @@ public class PsiSubstitutorImpl implements PsiSubstitutor {
@Override
public PsiType substitute(PsiType type) {
if (type == null) return null;
if (type == null) {
//noinspection ConstantConditions
return null;
}
assert type.isValid();
PsiType substituted = type.accept(myAddingBoundsSubstitutionVisitor);
return correctExternalSubstitution(substituted, type);
}
@@ -148,6 +152,7 @@ public class PsiSubstitutorImpl implements PsiSubstitutor {
if (newBound == null) {
return null;
}
assert newBound.isValid() : newBound.getClass();
if (newBound instanceof PsiWildcardType) {
return handleBoundComposition(wildcardType, (PsiWildcardType)newBound);
}
@@ -0,0 +1,89 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.editorActions
import com.intellij.openapi.editor.impl.AbstractEditorProcessingOnDocumentModificationTest
/**
* @author Denis Zhdanov
* @since 9/20/12 6:17 PM
*/
class FixDocCommentTest extends AbstractEditorProcessingOnDocumentModificationTest {
void testGenerateMethodDoc() {
doTest(
initial: '''\
class Test {
String test(int i) {
return "s";<caret>
}
}''',
expected: '''\
class Test {
/**
* <caret>
* @param i
* @return
*/
String test(int i) {
return "s";
}
}'''
)
}
void testGenerateFieldDoc() {
doTest(
initial: '''\
class Test {
int <caret>i;
}''',
expected: '''\
class Test {
/**
* <caret>
*/
int i;
}'''
)
}
void testGenerateClassDoc() {
doTest(
initial: '''\
class Test {
void test1() {}
<caret>
void test2() {}
}''',
expected: '''\
/**
* <caret>
*/
class Test {
void test1() {}
void test2() {}
}'''
)
}
private def doTest(Map args) {
configureFromFileText("${getTestName(false)}.java", args.initial)
executeAction(FixDocCommentAction.ACTION_ID)
checkResultByText(args.expected)
}
}
@@ -16,7 +16,10 @@
package com.intellij.lang.documentation;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -33,6 +36,28 @@ public interface CodeDocumentationProvider extends DocumentationProvider {
@Nullable
PsiComment findExistingDocComment(PsiComment contextElement);
/**
* Examines PSI hierarchy identified by the given 'start' element trying to find element which can be documented
* and it's doc comment (if any).
* <p/>
* Example:
* <pre>
* int test() {
* return [caret] 1;
* }
* </pre>
* PSI element at the caret (return element) is an entry point. This method is expected to return PSI method element
* and <code>'null'</code> as the existing doc comment then.
*
* @param startPoint start traversal point
* @return comment anchor which is a given element or its anchor if the one is found and its doc comment (if existing).
* This method may return <code>'null'</code> as an indication that no doc comment anchor and existing comment
* is available;
* returned pair must have non-null PSI element and nullable existing comment references then
*/
@Nullable
Pair<PsiElement, PsiComment> parseContext(@NotNull PsiElement startPoint);
/**
* Generate documentation comment content for given context.
* @param contextComment context psi comment
@@ -0,0 +1,253 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.editorActions;
import com.intellij.lang.*;
import com.intellij.lang.documentation.CodeDocumentationProvider;
import com.intellij.lang.documentation.CompositeDocumentationProvider;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorAction;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
/**
* Creates documentation comment for the current context if it's not created yet (e.g. the caret is inside a method which
* doesn't have a doc comment).
* <p/>
* Updates existing documentation comment if necessary if the one exists. E.g. we've changed method signature and want to remove all
* outdated parameters and create stubs for the new ones.
*
* @author Denis Zhdanov
* @since 9/20/12 10:15 AM
*/
public class FixDocCommentAction extends EditorAction {
@NotNull @NonNls public static final String ACTION_ID = "FixDocComment";
public FixDocCommentAction() {
super(new MyHandler());
}
private static final class MyHandler extends EditorActionHandler {
@Override
public void execute(Editor editor, DataContext dataContext) {
Project project = PlatformDataKeys.PROJECT.getData(dataContext);
if (project == null) {
return;
}
PsiFile psiFile = LangDataKeys.PSI_FILE.getData(dataContext);
if (psiFile == null) {
return;
}
process(psiFile, editor, project, editor.getCaretModel().getOffset());
}
}
private static void process(@NotNull final PsiFile file, @NotNull final Editor editor, @NotNull final Project project, int offset) {
PsiElement elementAtOffset = file.findElementAt(offset);
if (elementAtOffset == null) {
return;
}
Language language = PsiUtilBase.getLanguageAtOffset(file, offset);
final CodeDocumentationProvider docProvider;
final DocumentationProvider langDocumentationProvider = LanguageDocumentation.INSTANCE.forLanguage(language);
if (langDocumentationProvider instanceof CompositeDocumentationProvider) {
docProvider = ((CompositeDocumentationProvider)langDocumentationProvider).getFirstCodeDocumentationProvider();
}
else if (langDocumentationProvider instanceof CodeDocumentationProvider) {
docProvider = (CodeDocumentationProvider)langDocumentationProvider;
}
else {
docProvider = null;
}
if (docProvider == null) {
return;
}
final Pair<PsiElement, PsiComment> pair = docProvider.parseContext(elementAtOffset);
if (pair == null) {
return;
}
Commenter c = LanguageCommenters.INSTANCE.forLanguage(language);
if (!(c instanceof CodeDocumentationAwareCommenter)) {
return;
}
final CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter)c;
final Runnable command = new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
if (pair.second == null || pair.second.getTextRange().isEmpty()) {
generateComment(pair.first, editor, docProvider, commenter, project);
}
else {
fixCommentIfNecessary(pair.second);
}
}
});
}
};
CommandProcessor.getInstance().executeCommand(project, command, "Fix documentation", null);
}
/**
* Generates a comment if possible.
* <p/>
* It's assumed that this method {@link PsiDocumentManager#commitDocument(Document) syncs} all PSI-document
* changes during the processing.
*
* @param anchor target element for which a comment should be generated
* @param editor target editor
* @param commenter commenter to use
* @param project current project
*/
private static void generateComment(@NotNull PsiElement anchor,
@NotNull Editor editor,
@NotNull CodeDocumentationProvider documentationProvider,
@NotNull CodeDocumentationAwareCommenter commenter,
@NotNull Project project)
{
Document document = editor.getDocument();
int commentStartOffset = anchor.getTextRange().getStartOffset();
int lineStartOffset = document.getLineStartOffset(document.getLineNumber(commentStartOffset));
if (lineStartOffset > 0 && lineStartOffset < commentStartOffset) {
// Example:
// void test1() {
// }
// void test2() {
// <offset>
// }
// We want to insert the comment at the start of the line where 'test2()' is declared.
int nonWhiteSpaceOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), commentStartOffset - 1, " \t");
commentStartOffset = Math.max(nonWhiteSpaceOffset, lineStartOffset);
}
int commentBodyRelativeOffset = 0;
int caretOffsetToSet = -1;
StringBuilder buffer = new StringBuilder();
String commentPrefix = commenter.getDocumentationCommentPrefix();
if (commentPrefix != null) {
buffer.append(commentPrefix).append("\n");
commentBodyRelativeOffset += commentPrefix.length() + 1;
}
String linePrefix = commenter.getDocumentationCommentLinePrefix();
if (linePrefix != null) {
buffer.append(linePrefix);
commentBodyRelativeOffset += linePrefix.length();
caretOffsetToSet = commentStartOffset + commentBodyRelativeOffset;
}
buffer.append("\n");
commentBodyRelativeOffset++;
String commentSuffix = commenter.getDocumentationCommentSuffix();
if (commentSuffix != null) {
buffer.append(commentSuffix).append("\n");
}
if (buffer.length() <= 0) {
return;
}
document.insertString(commentStartOffset, buffer);
PsiDocumentManager docManager = PsiDocumentManager.getInstance(project);
docManager.commitDocument(document);
Pair<PsiElement, PsiComment> pair = documentationProvider.parseContext(anchor);
if (pair == null || pair.second == null) {
return;
}
String stub = documentationProvider.generateDocumentationContentStub(pair.second);
CaretModel caretModel = editor.getCaretModel();
if (stub != null) {
int insertionOffset = commentStartOffset + commentBodyRelativeOffset;
//if (CodeStyleSettingsManager.getSettings(project).JD_ADD_BLANK_AFTER_DESCRIPTION) {
// buffer.setLength(0);
// if (linePrefix != null) {
// buffer.append(linePrefix);
// }
// buffer.append("\n");
// buffer.append(stub);
// stub = buffer.toString();
//}
document.insertString(insertionOffset, stub);
docManager.commitDocument(document);
pair = documentationProvider.parseContext(anchor);
}
if (caretOffsetToSet >= 0) {
caretModel.moveToOffset(caretOffsetToSet);
}
if (pair == null || pair.second == null) {
return;
}
int start = Math.min(calcStartReformatOffset(pair.first), calcStartReformatOffset(pair.second));
int end = pair.second.getTextRange().getEndOffset();
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
codeStyleManager.reformatText(anchor.getContainingFile(), start, end);
int caretOffset = caretModel.getOffset();
if (caretOffset > 0 && caretOffset <= document.getTextLength()) {
char c = document.getCharsSequence().charAt(caretOffset - 1);
if (!StringUtil.isWhiteSpace(c)) {
document.insertString(caretOffset, " ");
caretModel.moveToOffset(caretOffset + 1);
}
}
}
private static int calcStartReformatOffset(@NotNull PsiElement element) {
int result = element.getTextRange().getStartOffset();
for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
if (e instanceof PsiWhiteSpace) {
result = e.getTextRange().getStartOffset();
}
else {
break;
}
}
return result;
}
private static void fixCommentIfNecessary(@NotNull PsiComment docComment) {
// TODO den implement
}
}
@@ -23,6 +23,7 @@ import com.intellij.ide.errorTreeView.impl.ErrorViewTextExporter;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
@@ -263,8 +264,7 @@ public class NewErrorTreeViewPanel extends JPanel implements DataProvider, Occur
int line,
int column,
@Nullable Object data) {
myErrorViewStructure
.addMessage(ErrorTreeElementKind.convertMessageFromCompilerErrorType(type), text, underFileGroup, file, line, column, data);
myErrorViewStructure.addMessage(ErrorTreeElementKind.convertMessageFromCompilerErrorType(type), text, underFileGroup, file, line, column, data);
myBuilder.updateTree();
}
@@ -275,11 +275,15 @@ public class NewErrorTreeViewPanel extends JPanel implements DataProvider, Occur
@Nullable String exportTextPrefix,
@Nullable String rendererTextPrefix,
@Nullable Object data) {
myErrorViewStructure.addNavigatableMessage(groupName, navigatable, ErrorTreeElementKind.convertMessageFromCompilerErrorType(type), text,
data,
exportTextPrefix == null ? "" : exportTextPrefix,
rendererTextPrefix == null ? "" : rendererTextPrefix,
data instanceof VirtualFile ? (VirtualFile)data : null);
VirtualFile file = data instanceof VirtualFile ? (VirtualFile)data : null;
if (file == null && navigatable instanceof OpenFileDescriptor) {
file = ((OpenFileDescriptor)navigatable).getFile();
}
final String exportPrefix = exportTextPrefix == null ? "" : exportTextPrefix;
final String renderPrefix = rendererTextPrefix == null ? "" : rendererTextPrefix;
final ErrorTreeElementKind kind = ErrorTreeElementKind.convertMessageFromCompilerErrorType(type);
myErrorViewStructure.addNavigatableMessage(groupName, navigatable, kind, text, data, exportPrefix, renderPrefix, file);
myBuilder.updateTree();
}
@@ -344,6 +344,8 @@ action.ShowFilePath.text=File _Path
action.ShowFilePath.description=Popup path of the current file for opening in external file manager
action.SelectIn.text=Se_lect In...
action.SelectIn.description=Select the current class or method in any view
action.FixDocComment.text=Fix doc comment
action.FixDocComment.description=Generate or fix doc comment for the current context
action.QuickJavaDoc.text=Quick _Documentation
action.QuickJavaDoc.description=Show a popup window with documentation for the symbol at caret
action.QuickImplementations.text=Quic_k Definition
@@ -158,6 +158,8 @@
<add-to-group group-id="ViewMenu" relative-to-action="QuickActions" anchor="after"/>
</group>
<action id="FixDocComment" class="com.intellij.codeInsight.editorActions.FixDocCommentAction"/>
<action id="ViewNavigationBar" class="com.intellij.ide.actions.ViewNavigationBarAction">
<add-to-group group-id="UIToggleActions" relative-to-action="ViewStatusBar" anchor="after"/>
@@ -40,7 +40,7 @@ public class SaveCommittingDocumentsVetoer implements FileDocumentSynchronizatio
return false;
}
if (beingCommitted instanceof Project) {
boolean allowSave = myAdapter.showAllowSaveDialog((Project) beingCommitted, Collections.singletonList(document));
boolean allowSave = myAdapter.showAllowSaveDialog(Collections.singletonMap(document, (Project)beingCommitted));
if (!allowSave) {
return false;
}
@@ -30,14 +30,15 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.UserDataHolderEx;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.changes.ui.CommitHelper;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class VetoSavingCommittingDocumentsAdapter implements ApplicationComponent {
static final Object SAVE_DENIED = new Object();
@@ -57,39 +58,49 @@ public class VetoSavingCommittingDocumentsAdapter implements ApplicationComponen
ApplicationManager.getApplication().getMessageBus().connect().subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() {
@Override
public void beforeAllDocumentsSaving() {
List<Document> documentsToWarn = new ArrayList<Document>();
final Document[] unsavedDocuments = myFileDocumentManager.getUnsavedDocuments();
Project commitOwnerProject = null;
for (Document unsavedDocument : unsavedDocuments) {
final Object data = unsavedDocument.getUserData(CommitHelper.DOCUMENT_BEING_COMMITTED_KEY);
if (data instanceof Project) {
commitOwnerProject = (Project) data;
documentsToWarn.add(unsavedDocument);
}
}
Map<Document, Project> documentsToWarn = getDocumentsBeingCommitted();
if (!documentsToWarn.isEmpty()) {
boolean allowSave = showAllowSaveDialog(commitOwnerProject, documentsToWarn);
for (Document document : documentsToWarn) {
document.putUserData(CommitHelper.DOCUMENT_BEING_COMMITTED_KEY, allowSave ? null : SAVE_DENIED);
}
boolean allowSave = showAllowSaveDialog(documentsToWarn);
updateSaveability(documentsToWarn, allowSave);
}
}
});
}
private Map<Document, Project> getDocumentsBeingCommitted() {
Map<Document, Project> documentsToWarn = ContainerUtil.newHashMap();
for (Document unsavedDocument : myFileDocumentManager.getUnsavedDocuments()) {
final Object data = unsavedDocument.getUserData(CommitHelper.DOCUMENT_BEING_COMMITTED_KEY);
if (data instanceof Project) {
documentsToWarn.put(unsavedDocument, (Project)data);
}
}
return documentsToWarn;
}
private static void updateSaveability(Map<Document, Project> documentsToWarn, boolean allowSave) {
Object newValue = allowSave ? null : SAVE_DENIED;
for (Document document : documentsToWarn.keySet()) {
Project oldData = documentsToWarn.get(document);
//the committing thread could have finished already and file is not being committed anymore
((UserDataHolderEx)document).replace(CommitHelper.DOCUMENT_BEING_COMMITTED_KEY, oldData, newValue);
}
}
public void disposeComponent() {
}
boolean showAllowSaveDialog(Project project, List<Document> documentsToWarn) {
boolean showAllowSaveDialog(Map<Document, Project> documentsToWarn) {
StringBuilder messageBuilder = new StringBuilder("The following " + (documentsToWarn.size() == 1 ? "file is" : "files are") +
" currently being committed to the VCS. " +
"Saving now could cause inconsistent data to be committed.\n");
for (Document document : documentsToWarn) {
for (Document document : documentsToWarn.keySet()) {
final VirtualFile file = myFileDocumentManager.getFile(document);
messageBuilder.append(FileUtil.toSystemDependentName(file.getPath())).append("\n");
}
messageBuilder.append("Save the ").append(documentsToWarn.size() == 1 ? "file" : "files").append(" now?");
Project project = documentsToWarn.values().iterator().next();
int rc = Messages.showOkCancelDialog(project, messageBuilder.toString(), "Save Files During Commit", "Save Now", "Postpone Save",
Messages.getQuestionIcon());
return rc == 0;
@@ -25,6 +25,7 @@ import com.intellij.lang.documentation.CompositeDocumentationProvider;
import com.intellij.lang.documentation.ExternalDocumentationProvider;
import com.intellij.lang.java.JavaDocumentationProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
@@ -37,6 +38,7 @@ import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.dsl.CustomMembersGenerator;
import org.jetbrains.plugins.groovy.dsl.holders.NonCodeMembersHolder;
@@ -459,6 +461,17 @@ public class GroovyDocumentationProvider implements CodeDocumentationProvider, E
return null;
}
@Nullable
@Override
public Pair<PsiElement, PsiComment> parseContext(@NotNull PsiElement startPoint) {
for (PsiElement e = startPoint; e != null; e = e.getParent()) {
if (e instanceof GrDocCommentOwner) {
return Pair.<PsiElement, PsiComment>create(e, ((GrDocCommentOwner)e).getDocComment());
}
}
return null;
}
public String generateDocumentationContentStub(PsiComment contextComment) {
if (!(contextComment instanceof GrDocComment)) {
return null;
@@ -103,7 +103,7 @@ public class DFAEngine<E> {
while (!workList.isEmpty()) {
count++;
if (timeout && count % 50 == 0 && tm.getCurrentThreadUserTime() - startTime > ourTimeLimit) return null;
if (timeout && count % 512 == 0 && tm.getCurrentThreadUserTime() - startTime > ourTimeLimit) return null;
ProgressManager.checkCanceled();
final Instruction curr = workList.remove();
@@ -88,6 +88,7 @@ public class DFAType {
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof DFAType)) return false;
final DFAType other = (DFAType)obj;
@@ -151,11 +152,13 @@ public class DFAType {
}
private static boolean eq(PsiType t1, PsiType t2) {
return Comparing.equal(TypeConversionUtil.erasure(t1), TypeConversionUtil.erasure(t2));
return t1 == t2 || Comparing.equal(TypeConversionUtil.erasure(t1), TypeConversionUtil.erasure(t2));
}
@Nullable
public static DFAType create(DFAType t1, DFAType t2, PsiManager manager) {
if (t1.equals(t2)) return t1;
final PsiType primary = TypesUtil.getLeastUpperBoundNullable(t1.primary, t2.primary, manager);
final DFAType type = new DFAType(primary);
@@ -114,7 +114,8 @@ public class TypeInferenceHelper {
final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(refExpr);
if (scope == null) return null;
return inferVariableTypes(scope).getInferredType(refExpr.getReferenceName(), ControlFlowUtils.findRWInstruction(refExpr, scope.getControlFlow()));
return getInferenceCache(scope).getInferredType(refExpr.getReferenceName(), ControlFlowUtils
.findRWInstruction(refExpr, scope.getControlFlow()));
}
@Nullable
@@ -122,18 +123,16 @@ public class TypeInferenceHelper {
final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(place);
if (scope == null) return null;
return inferVariableTypes(scope).getInferredType(variableName, ControlFlowUtils.findNearestInstruction(place, scope.getControlFlow()));
return getInferenceCache(scope).getInferredType(variableName, ControlFlowUtils.findNearestInstruction(place, scope.getControlFlow()));
}
@NotNull
private static InferenceResult inferVariableTypes(final GrControlFlowOwner scope) {
return CachedValuesManager.getManager(scope.getProject()).getCachedValue(scope, new CachedValueProvider<InferenceResult>() {
private static InferenceCache getInferenceCache(final GrControlFlowOwner scope) {
return CachedValuesManager.getManager(scope.getProject()).getCachedValue(scope, new CachedValueProvider<InferenceCache>() {
@Nullable
@Override
public Result<InferenceResult> compute() {
Instruction[] flow = scope.getControlFlow();
List<Map<String, PsiType>> list = performTypeDfa(scope, flow);
return Result.create(new InferenceResult(flow, list), PsiModificationTracker.MODIFICATION_COUNT);
public Result<InferenceCache> compute() {
return Result.create(new InferenceCache(scope), PsiModificationTracker.MODIFICATION_COUNT);
}
});
}
@@ -299,27 +298,17 @@ public class TypeInferenceHelper {
return null;
}
@Nullable
private static List<Map<String, PsiType>> performTypeDfa(GrControlFlowOwner owner, Instruction[] flow) {
final TypeDfaInstance dfaInstance = new TypeDfaInstance(owner, flow);
final TypesSemilattice semilattice = new TypesSemilattice(owner.getManager());
List<TypeDfaState> states = new DFAEngine<TypeDfaState>(flow, dfaInstance, semilattice).performDFAWithTimeout();
if (states == null) return null;
List<Map<String, PsiType>> result = ContainerUtil.newArrayList();
for (int i = 0; i < states.size(); i++) {
result.add(states.get(i).getBindings(flow[i]));
}
return result;
}
static class TypeDfaInstance implements DfaInstance<TypeDfaState> {
private final GrControlFlowOwner myScope;
private final Instruction[] myFlow;
private final Set<Instruction> myInteresting;
private final InferenceCache myCache;
TypeDfaInstance(GrControlFlowOwner scope, Instruction[] flow) {
TypeDfaInstance(GrControlFlowOwner scope, Instruction[] flow, Set<Instruction> interesting, InferenceCache cache) {
myScope = scope;
myFlow = flow;
myInteresting = interesting;
myCache = cache;
}
public void fun(final TypeDfaState state, final Instruction instruction) {
@@ -335,31 +324,48 @@ public class TypeInferenceHelper {
final String varName = instruction.getVariableName();
if (varName == null) return;
state.putType(varName, doInference(state.getBindings(instruction), new NullableComputable<DFAType>() {
updateVariableType(state, instruction, varName, new NullableComputable<DFAType>() {
@Override
public DFAType compute() {
ReadWriteVariableInstruction originalInstr = instruction.getInstructionToMixin(myFlow);
assert originalInstr != null && !originalInstr.isWrite();
DFAType original = state.getVariableType(varName).negate(originalInstr);
DFAType original = state.getVariableType(varName);
if (original == null) {
original = DFAType.create(null);
}
original = original.negate(originalInstr);
original.addMixin(instruction.inferMixinType(), instruction.getConditionInstruction());
return original;
}
}));
});
}
private void handleVariableWrite(TypeDfaState state, ReadWriteVariableInstruction instruction) {
final PsiElement element = instruction.getElement();
if (element != null && instruction.isWrite()) {
state.putType(instruction.getVariableName(), doInference(state.getBindings(instruction), new Computable<DFAType>() {
updateVariableType(state, instruction, instruction.getVariableName(), new Computable<DFAType>() {
@Override
public DFAType compute() {
return DFAType.create(TypesUtil.boxPrimitiveType(getInitializerType(element), myScope.getManager(), myScope.getResolveScope()));
}
}));
});
}
}
private void updateVariableType(TypeDfaState state, Instruction instruction, String variableName, Computable<DFAType> computation) {
if (!myInteresting.contains(instruction)) {
state.removeBinding(variableName);
return;
}
DFAType type = myCache.getCachedInferredType(variableName, instruction);
if (type == null) {
type = doInference(state.getBindings(instruction), computation);
}
state.putType(variableName, type);
}
@NotNull
public TypeDfaState initial() {
return new TypeDfaState();
@@ -371,22 +377,135 @@ public class TypeInferenceHelper {
}
private static class InferenceResult {
private static class InferenceCache {
final GrControlFlowOwner scope;
final Instruction[] flow;
final List<Map<String, PsiType>> varTypes;
final AtomicReference<List<TypeDfaState>> varTypes;
final ConcurrentHashSet<Instruction> tooComplex = new ConcurrentHashSet<Instruction>();
InferenceResult(Instruction[] flow, @Nullable List<Map<String, PsiType>> varTypes) {
this.flow = flow;
this.varTypes = varTypes;
InferenceCache(final GrControlFlowOwner scope) {
this.scope = scope;
this.flow = scope.getControlFlow();
List<TypeDfaState> noTypes = new ArrayList<TypeDfaState>();
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < flow.length; i++) {
noTypes.add(new TypeDfaState());
}
varTypes = new AtomicReference<List<TypeDfaState>>(noTypes);
}
@Nullable
private PsiType getInferredType(String variableName, Instruction instruction) {
if (instruction == null || varTypes == null) return null;
private PsiType getInferredType(@Nullable String variableName, @Nullable Instruction instruction) {
if (instruction == null || variableName == null) return null;
if (tooComplex.contains(instruction)) return null;
return varTypes.get(instruction.num()).get(variableName);
TypeDfaState cache = varTypes.get().get(instruction.num());
if (!cache.containsVariable(variableName)) {
Set<Instruction> interesting = collectRequiredInstructions(instruction, variableName);
List<TypeDfaState> dfaResult = performTypeDfa(scope, flow, interesting);
if (dfaResult == null) {
tooComplex.addAll(interesting);
} else {
cacheDfaResult(dfaResult);
}
}
DFAType dfaType = getCachedInferredType(variableName, instruction);
return dfaType == null ? null : dfaType.getResultType();
}
@Nullable
private List<TypeDfaState> performTypeDfa(GrControlFlowOwner owner, Instruction[] flow, Set<Instruction> interesting) {
final TypeDfaInstance dfaInstance = new TypeDfaInstance(owner, flow, interesting, this);
final TypesSemilattice semilattice = new TypesSemilattice(owner.getManager());
return new DFAEngine<TypeDfaState>(flow, dfaInstance, semilattice).performDFAWithTimeout();
}
@Nullable
DFAType getCachedInferredType(@NotNull String variableName, @NotNull Instruction instruction) {
DFAType dfaType = varTypes.get().get(instruction.num()).getVariableType(variableName);
return dfaType == null ? null : dfaType.negate(instruction);
}
private Set<Instruction> collectRequiredInstructions(Instruction instruction, String variableName) {
Pair<ReachingDefinitionsDfaInstance, List<DefinitionMap>> defUse = getDefUseMaps(scope);
Set<Instruction> interesting = ContainerUtil.newHashSet(instruction);
LinkedList<Pair<Instruction,String>> queue = ContainerUtil.newLinkedList();
queue.add(Pair.create(instruction, variableName));
while (!queue.isEmpty()) {
Pair<Instruction, String> pair = queue.removeFirst();
for (Pair<Instruction, String> dep : findDependencies(defUse, pair.first, pair.second)) {
if (interesting.add(dep.first)) {
queue.addLast(dep);
}
}
}
return interesting;
}
private Set<Pair<Instruction,String>> findDependencies(Pair<ReachingDefinitionsDfaInstance, List<DefinitionMap>> defUse,
Instruction insn,
String varName) {
int[] definitions = defUse.second.get(insn.num()).getDefinitions(defUse.first.getVarIndex(varName));
if (definitions == null) return Collections.emptySet();
HashSet<Pair<Instruction, String>> pairs = ContainerUtil.newHashSet();
for (int defIndex : definitions) {
Instruction write = flow[defIndex];
pairs.add(Pair.create(write, varName));
PsiElement statement = findDependencyScope(write.getElement());
pairs.addAll(findAllInstructionsInside(statement));
}
return pairs;
}
private List<Pair<Instruction, String>> findAllInstructionsInside(PsiElement scope) {
final List<Pair<Instruction, String>> result = ContainerUtil.newArrayList();
scope.accept(new PsiRecursiveElementWalkingVisitor() {
@Override
public void visitElement(PsiElement element) {
if (element instanceof GrReferenceExpression && !((GrReferenceExpression)element).isQualified()) {
String varName = ((GrReferenceExpression)element).getReferenceName();
if (varName != null) {
for (Instruction dependency : ControlFlowUtils.findAllInstructions(element, flow)) {
result.add(Pair.create(dependency, varName));
}
}
}
super.visitElement(element);
}
});
return result;
}
@Nullable
private static PsiElement findDependencyScope(PsiElement element) {
return PsiTreeUtil.findFirstParent(element, new Condition<PsiElement>() {
@Override
public boolean value(PsiElement element) {
return org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil.isExpressionStatement(element) ||
!(element.getParent() instanceof GrExpression);
}
});
}
private void cacheDfaResult(List<TypeDfaState> dfaResult) {
while (true) {
List<TypeDfaState> oldTypes = varTypes.get();
if (varTypes.compareAndSet(oldTypes, addDfaResult(dfaResult, oldTypes))) {
return;
}
}
}
private static List<TypeDfaState> addDfaResult(List<TypeDfaState> dfaResult, List<TypeDfaState> oldTypes) {
List<TypeDfaState> newTypes = new ArrayList<TypeDfaState>(oldTypes);
for (int i = 0; i < dfaResult.size(); i++) {
newTypes.set(i, newTypes.get(i).mergeWith(dfaResult.get(i)));
}
return newTypes;
}
}
}
@@ -19,6 +19,7 @@ import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiType;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAType;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.Semilattice;
@@ -63,6 +64,15 @@ class TypeDfaState {
myVarTypes = ContainerUtil.newHashMap(another.myVarTypes);
}
TypeDfaState mergeWith(TypeDfaState another) {
if (another.myVarTypes.isEmpty()) {
return this;
}
TypeDfaState state = new TypeDfaState(this);
state.myVarTypes.putAll(another.myVarTypes);
return state;
}
void joinState(TypeDfaState another, PsiManager manager) {
for (Map.Entry<String, DFAType> entry : another.myVarTypes.entrySet()) {
final String name = entry.getKey();
@@ -83,10 +93,9 @@ class TypeDfaState {
return myVarTypes.equals(another.myVarTypes);
}
@NotNull
@Nullable
DFAType getVariableType(String variableName) {
DFAType type = myVarTypes.get(variableName);
return type == null ? DFAType.create(null) : type;
return myVarTypes.get(variableName);
}
Map<String, PsiType> getBindings(Instruction instruction) {
@@ -98,7 +107,7 @@ class TypeDfaState {
return map;
}
void putType(String variableName, DFAType type) {
void putType(String variableName, @Nullable DFAType type) {
myVarTypes.put(variableName, type);
}
@@ -106,4 +115,12 @@ class TypeDfaState {
public String toString() {
return "TypeDfaState{" + myVarTypes + '}';
}
public boolean containsVariable(@NotNull String variableName) {
return myVarTypes.containsKey(variableName);
}
public void removeBinding(String variableName) {
myVarTypes.remove(variableName);
}
}
@@ -20,7 +20,11 @@ import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
@@ -36,6 +40,8 @@ import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor;
import java.util.List;
/**
* @author ilyas
*/
@@ -82,56 +88,115 @@ public class GrImportStatementImpl extends GroovyPsiElementImpl implements GrImp
if (ref == null) return true;
if (isStatic()) {
GrCodeReferenceElement qualifier = ref.getQualifier();
if (qualifier == null) return true;
PsiElement resolved = qualifier.resolve();
if (!(resolved instanceof PsiClass)) return true;
PsiClass clazz = (PsiClass)resolved;
state = state.put(ResolverProcessor.RESOLVE_CONTEXT, this);
final String refName = ref.getReferenceName();
if (nameHint == null || name.equals(nameHint.getName(state))) {
final PsiField field = clazz.findFieldByName(refName, true);
if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) {
if (!processor.execute(field, state)) return false;
}
for (PsiMethod method : clazz.findMethodsByName(refName, true)) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
if (!processor.execute(method, state)) return false;
}
}
final PsiClass innerClass = clazz.findInnerClassByName(refName, true);
if (innerClass != null && innerClass.hasModifierProperty(PsiModifier.STATIC) && !processor.execute(innerClass, state)) return false;
}
final PsiMethod getter = GroovyPropertyUtils.findPropertyGetter(clazz, refName, true, true);
if (getter != null &&
(nameHint == null || name.equals(GroovyPropertyUtils.getPropertyNameByGetterName(nameHint.getName(state), true)))) {
if (!processor.execute(getter, state)) return false;
}
final PsiMethod setter = GroovyPropertyUtils.findPropertySetter(clazz, refName, true, true);
if (setter != null &&
(nameHint == null || name.equals(GroovyPropertyUtils.getPropertyNameBySetterName(nameHint.getName(state))))) {
if (!processor.execute(setter, state)) return false;
}
return processSingleStaticImport(processor, state, name, nameHint, ref);
}
else { //class import statement
if (nameHint == null || name.equals(nameHint.getName(state))) {
final PsiElement resolved = ref.resolve();
if (resolved instanceof PsiClass) {
if (!isAliasedImport() && isFromSamePackage((PsiClass)resolved)) return true; //don't process classes from the same package because such import statements are ignored by compiler
state = state.put(ResolverProcessor.RESOLVE_CONTEXT, this);
if (!processor.execute(resolved, state)) return false;
}
}
if (nameHint == null || name.equals(nameHint.getName(state))) {
return processSingleClassImport(processor, state, ref);
}
return true;
}
@Nullable
private PsiClass resolveQualifier() {
return CachedValuesManager.getManager(getProject()).getCachedValue(this, new CachedValueProvider<PsiClass>() {
@Nullable
@Override
public Result<PsiClass> compute() {
GrCodeReferenceElement reference = getImportReference();
GrCodeReferenceElement qualifier = reference == null ? null : reference.getQualifier();
PsiElement target = qualifier == null ? null : qualifier.resolve();
PsiClass clazz = target instanceof PsiClass ? (PsiClass)target : null;
return Result.create(clazz, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, GrImportStatementImpl.this);
}
});
}
private static List<PsiMember> getAllStaticMembers(final PsiClass clazz) {
return CachedValuesManager.getManager(clazz.getProject()).getCachedValue(clazz, new CachedValueProvider<List<PsiMember>>() {
@Nullable
@Override
public Result<List<PsiMember>> compute() {
List<PsiMember> result = ContainerUtil.newArrayList();
for (PsiMethod method : clazz.getAllMethods()) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
result.add(method);
}
}
for (PsiField field : clazz.getAllFields()) {
if (field.hasModifierProperty(PsiModifier.STATIC)) {
result.add(field);
}
}
for (PsiClass inner : clazz.getAllInnerClasses()) {
if (inner.hasModifierProperty(PsiModifier.STATIC)) {
result.add(inner);
}
}
return Result.create(result, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, clazz);
}
});
}
private boolean processSingleStaticImport(PsiScopeProcessor processor,
ResolveState state,
String importedName,
NameHint nameHint,
GrCodeReferenceElement ref) {
PsiClass clazz = resolveQualifier();
if (clazz == null) return true;
state = state.put(ResolverProcessor.RESOLVE_CONTEXT, this);
String hintName = nameHint == null ? null : nameHint.getName(state);
final String refName = ref.getReferenceName();
if (hintName == null || importedName.equals(hintName)) {
final PsiField field = clazz.findFieldByName(refName, true);
if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) {
if (!processor.execute(field, state)) return false;
}
for (PsiMethod method : clazz.findMethodsByName(refName, true)) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
if (!processor.execute(method, state)) return false;
}
}
final PsiClass innerClass = clazz.findInnerClassByName(refName, true);
if (innerClass != null && innerClass.hasModifierProperty(PsiModifier.STATIC) && !processor.execute(innerClass, state)) return false;
}
String propByGetter = hintName == null ? null : GroovyPropertyUtils.getPropertyNameByGetterName(hintName, true);
String propBySetter = hintName == null ? null : GroovyPropertyUtils.getPropertyNameBySetterName(hintName);
for (PsiMember member : getAllStaticMembers(clazz)) {
if (!(member instanceof PsiMethod)) {
continue;
}
PsiMethod method = (PsiMethod)member;
if ((propByGetter == null || importedName.equals(propByGetter)) && GroovyPropertyUtils.isSimplePropertyGetter(method, refName) ||
(propBySetter == null || importedName.equals(propBySetter)) && GroovyPropertyUtils.isSimplePropertySetter(method, refName)) {
if (method.hasModifierProperty(PsiModifier.STATIC) && !processor.execute(method, state)) {
return false;
}
}
}
return true;
}
private boolean processSingleClassImport(PsiScopeProcessor processor, ResolveState state, GrCodeReferenceElement ref) {
final PsiElement resolved = ref.resolve();
if (!(resolved instanceof PsiClass)) {
return true;
}
if (!isAliasedImport() && isFromSamePackage((PsiClass)resolved)) {
return true; //don't process classes from the same package because such import statements are ignored by compiler
}
return processor.execute(resolved, state.put(ResolverProcessor.RESOLVE_CONTEXT, this));
}
private boolean isFromSamePackage(PsiClass resolved) {
final String qualifiedName = resolved.getQualifiedName();
final String packageName = ((GroovyFile)getContainingFile()).getPackageName();
@@ -168,18 +233,9 @@ public class GrImportStatementImpl extends GroovyPsiElementImpl implements GrImp
}
private static boolean processAllMembers(PsiScopeProcessor processor, PsiClass clazz, ResolveState state) {
for (PsiField field : clazz.getAllFields()) {
if (field.hasModifierProperty(PsiModifier.STATIC) && !ResolveUtil.processElement(processor, field, state)) return false;
for (PsiMember member : getAllStaticMembers(clazz)) {
if (!ResolveUtil.processElement(processor, (PsiNamedElement)member, state)) return false;
}
for (PsiMethod method : clazz.getAllMethods()) {
if (method.hasModifierProperty(PsiModifier.STATIC) && !ResolveUtil.processElement(processor, method, state)) return false;
}
for (PsiClass inner : clazz.getAllInnerClasses()) {
if (inner.hasModifierProperty(PsiModifier.STATIC) && !ResolveUtil.processElement(processor, inner, state)) return false;
}
return true;
}
@@ -228,9 +284,7 @@ public class GrImportStatementImpl extends GroovyPsiElementImpl implements GrImp
resolved = ref.resolve();
}
else {
final GrCodeReferenceElement qualifier = ref.getQualifier();
if (qualifier == null) return null;
resolved = qualifier.resolve();
resolved = resolveQualifier();
}
return resolved instanceof PsiClass ? (PsiClass)resolved : null;
@@ -26,9 +26,8 @@ import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
@@ -207,11 +206,16 @@ public class GrClassImplUtil {
}
@NotNull
public static PsiMethod[] getAllMethods(GrTypeDefinition grType) {
List<PsiMethod> allMethods = new ArrayList<PsiMethod>();
getAllMethodsInner(grType, allMethods, new HashSet<PsiClass>());
return allMethods.toArray(new PsiMethod[allMethods.size()]);
public static PsiMethod[] getAllMethods(final GrTypeDefinition grType) {
return CachedValuesManager.getManager(grType.getProject()).getCachedValue(grType, new CachedValueProvider<PsiMethod[]>() {
@Nullable
@Override
public Result<PsiMethod[]> compute() {
List<PsiMethod> list = ContainerUtil.newArrayList();
getAllMethodsInner(grType, list, new HashSet<PsiClass>());
return Result.create(list.toArray(new PsiMethod[list.size()]), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, grType);
}
});
}
@NotNull
@@ -207,7 +207,7 @@ class SomeClass {
measureHighlighting(text, 8000)
}
public void "_test infer only the variable types that are needed"() {
public void "test infer only the variable types that are needed"() {
addGdsl '''contribute(currentType(String.name)) {
println 'sleeping'
Thread.sleep(1000)
@@ -15,6 +15,7 @@
*/
package com.intellij.lang.properties;
import com.intellij.openapi.application.PluginPathManager;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
/**
@@ -22,10 +23,9 @@ import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCa
*/
public class PropertiesFoldingTest extends LightPlatformCodeInsightFixtureTestCase {
private static final String BASE_PATH = "/plugins/properties/testData/propertiesFile/folding/";
protected String getBasePath() {
return BASE_PATH;
@Override
protected String getTestDataPath() {
return PluginPathManager.getPluginHomePath("properties") + "/testData/propertiesFile/folding/";
}
private void doTest() {