From 6ff151f70334ab0e9fd6771dd4398380cb624766 Mon Sep 17 00:00:00 2001 From: "alexey.afanasiev" Date: Wed, 6 Feb 2019 23:56:22 +0300 Subject: [PATCH 1/5] [groovy] IDEA-202693 Support lambda in control flow builder --- .../lang/psi/api/GrFunctionalExpression.java | 8 ++ .../groovy/lang/psi/api/GrLambdaBody.java | 9 ++ .../lang/psi/api/GrLambdaExpression.java | 6 ++ .../statements/blocks/GrClosableBlock.java | 1 + .../lang/psi/controlFlow/Instruction.java | 1 + .../controlFlow/impl/ControlFlowBuilder.java | 82 ++++++++++++------- .../lang/psi/impl/GrLambdaBodyBlockImpl.java | 30 ------- .../lang/psi/impl/GrLambdaBodyBlockImpl.kt | 24 ++++++ .../psi/impl/GrLambdaBodyExpressionImpl.kt | 6 ++ .../lang/psi/impl/GrLambdaExpressionImpl.kt | 17 ++-- .../impl/GrReassignedLocalVarsChecker.java | 21 ++++- 11 files changed, 132 insertions(+), 73 deletions(-) delete mode 100644 plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyBlockImpl.java create mode 100644 plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyBlockImpl.kt diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrFunctionalExpression.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrFunctionalExpression.java index fd0be275907f..344c31247c59 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrFunctionalExpression.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrFunctionalExpression.java @@ -1,10 +1,12 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.lang.psi.api; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrParameterListOwner; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; @@ -17,4 +19,10 @@ public interface GrFunctionalExpression extends GrExpression, GrParameterListOwn @Nullable PsiType getReturnType(); + + @NotNull + GrStatement[] getStatements(); + + @Nullable + PsiElement getArrow(); } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrLambdaBody.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrLambdaBody.java index 4ccb559b9d88..42f851410538 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrLambdaBody.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrLambdaBody.java @@ -1,10 +1,19 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.lang.psi.api; +import com.intellij.psi.PsiType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; /** * Represents a Groovy lambda expression body. */ public interface GrLambdaBody extends GrControlFlowOwner { + + @NotNull + GrLambdaExpression getLambdaExpression(); + + @Nullable + PsiType getReturnType(); } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrLambdaExpression.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrLambdaExpression.java index e4c1bd6b5976..c72a5ec8a402 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrLambdaExpression.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/GrLambdaExpression.java @@ -1,6 +1,8 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.lang.psi.api; +import com.intellij.psi.*; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -14,4 +16,8 @@ public interface GrLambdaExpression extends GrFunctionalExpression { */ @Nullable GrLambdaBody getBody(); + + @Override + @NotNull + PsiElement getArrow(); } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/blocks/GrClosableBlock.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/blocks/GrClosableBlock.java index faac782757b8..980b4bc1c837 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/blocks/GrClosableBlock.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/blocks/GrClosableBlock.java @@ -40,6 +40,7 @@ public interface GrClosableBlock extends GrFunctionalExpression, GrCodeBlock { @Override GrParameter[] getAllParameters(); + @Override @Nullable PsiElement getArrow(); diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/Instruction.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/Instruction.java index d67595e7e773..bc8834df1cf7 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/Instruction.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/Instruction.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.Nullable; * @author ven */ public interface Instruction { + Instruction[] EMPTY_ARRAY = new Instruction[0]; @NotNull Iterable successors(@NotNull CallEnvironment environment); diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java index 0d28790586ae..4dcf564be6b9 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java @@ -17,11 +17,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; +import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; -import org.jetbrains.plugins.groovy.lang.psi.api.GrInExpression; -import org.jetbrains.plugins.groovy.lang.psi.api.GrTryResourceList; +import org.jetbrains.plugins.groovy.lang.psi.api.*; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrCondition; import org.jetbrains.plugins.groovy.lang.psi.api.statements.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; @@ -99,8 +99,9 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { final PsiElement lbrace = block.getLBrace(); if (lbrace != null && parent instanceof GrMethod) { for (GrParameter parameter : ((GrMethod)parent).getParameters()) { - if (myPolicy.isVariableInitialized(parameter)) { - addNode(new ReadWriteVariableInstruction(parameter.getName(), parameter, ReadWriteVariableInstruction.WRITE)); + String parameterName = parameter.getName(); + if (myPolicy.isVariableInitialized(parameter) && parameterName != null) { + addNode(new ReadWriteVariableInstruction(parameterName, parameter, ReadWriteVariableInstruction.WRITE)); } } } @@ -114,6 +115,12 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { } } + @Override + public void visitLambdaBody(@NotNull GrLambdaBody body) { + addFunctionalExpressionParameters(body.getLambdaExpression()); + addControlFlowInstructions(body); + } + @Override public void visitFile(@NotNull GroovyFileBase file) { super.visitFile(file); @@ -133,14 +140,20 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { } public Instruction[] buildControlFlow(GroovyPsiElement scope) { + if (scope instanceof GrLambdaExpression) { + GrLambdaBody body = ((GrLambdaExpression)scope).getBody(); + return body != null ? body.getControlFlow() : Instruction.EMPTY_ARRAY; + } myFinallyCount = 0; myInstructionNumber = 0; myScope = scope; startNode(null); + if (scope instanceof GrClosableBlock) { - buildFlowForClosure((GrClosableBlock)scope); + addFunctionalExpressionParameters((GrFunctionalExpression)scope); + addControlFlowInstructions((GrControlFlowOwner)scope); } else { scope.accept(this); @@ -150,7 +163,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { checkPending(end); //collect return edges - return assertValidPsi(myInstructions.toArray(new Instruction[0])); + return assertValidPsi(myInstructions.toArray(Instruction.EMPTY_ARRAY)); } public static Instruction[] assertValidPsi(Instruction[] instructions) { @@ -163,16 +176,9 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { return instructions; } - private void buildFlowForClosure(final GrClosableBlock closure) { - for (GrParameter parameter : closure.getAllParameters()) { - if (myPolicy.isVariableInitialized(parameter)) { - addNode(new ReadWriteVariableInstruction(parameter.getName(), parameter, ReadWriteVariableInstruction.WRITE)); - } - } - addNode(new ReadWriteVariableInstruction("owner", closure.getLBrace(), ReadWriteVariableInstruction.WRITE)); - - PsiElement child = closure.getFirstChild(); + private void addControlFlowInstructions(final GrControlFlowOwner owner) { + PsiElement child = owner.getFirstChild(); while (child != null) { if (child instanceof GroovyPsiElement) { ((GroovyPsiElement)child).accept(this); @@ -180,12 +186,25 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { child = child.getNextSibling(); } - final GrStatement[] statements = closure.getStatements(); + final GrStatement[] statements = owner.getStatements(); if (statements.length > 0) { handlePossibleReturn(statements[statements.length - 1]); } } + private void addFunctionalExpressionParameters(GrFunctionalExpression expression) { + for (GrParameter parameter : expression.getAllParameters()) { + String parameterName = parameter.getName(); + if (myPolicy.isVariableInitialized(parameter) && parameterName != null) { + addNode(new ReadWriteVariableInstruction(parameterName, parameter, ReadWriteVariableInstruction.WRITE)); + } + } + + PsiElement anchor = expression.getArrow(); + if (expression instanceof GrClosableBlock) anchor = ((GrClosableBlock)expression).getLBrace(); + addNode(new ReadWriteVariableInstruction("owner", anchor, ReadWriteVariableInstruction.WRITE)); + } + private T addNode(T instruction) { instruction.setNumber(myInstructionNumber++); myInstructions.add(instruction); @@ -211,30 +230,37 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { } } + @Override + public void visitLambdaExpression(@NotNull GrLambdaExpression expression) { + GrLambdaBody body = expression.getBody(); + if (body == null) return; + ReadWriteVariableInstruction[] reads = ControlFlowBuilderUtil.getReadsWithoutPriorWrites(body.getControlFlow(), false); + addReadFromNestedControlFlow(expression, reads); + } + @Override public void visitClosure(@NotNull GrClosableBlock closure) { //do not go inside closures except gstring injections if (closure.getParent() instanceof GrStringInjection) { - for (GrParameter parameter : closure.getAllParameters()) { - if (myPolicy.isVariableInitialized(parameter)) { - addNode(new ReadWriteVariableInstruction(parameter.getName(), parameter, ReadWriteVariableInstruction.WRITE)); - } - } - addNode(new ReadWriteVariableInstruction("owner", closure.getLBrace(), ReadWriteVariableInstruction.WRITE)); + addFunctionalExpressionParameters(closure); super.visitClosure(closure); return; } ReadWriteVariableInstruction[] reads = ControlFlowBuilderUtil.getReadsWithoutPriorWrites(closure.getControlFlow(), false); + addReadFromNestedControlFlow(closure, reads); + } + + private void addReadFromNestedControlFlow(@NotNull PsiElement anchor, @NotNull ReadWriteVariableInstruction[] reads) { for (ReadWriteVariableInstruction read : reads) { PsiElement element = read.getElement(); if (!(element instanceof GrReferenceExpression) || myPolicy.isReferenceAccepted((GrReferenceExpression)element)) { - addNodeAndCheckPending(new ReadWriteVariableInstruction(read.getVariableName(), closure, ReadWriteVariableInstruction.READ)); + addNodeAndCheckPending(new ReadWriteVariableInstruction(read.getVariableName(), anchor, ReadWriteVariableInstruction.READ)); } } - addNodeAndCheckPending(new InstructionImpl(closure)); + addNodeAndCheckPending(new InstructionImpl(anchor)); } @Override @@ -1186,13 +1212,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { final Set vars = collectUsedVariableWithoutInitialization(typeDefinition); - for (ReadWriteVariableInstruction var : vars) { - PsiElement element = var.getElement(); - if (!(element instanceof GrReferenceExpression) || myPolicy.isReferenceAccepted((GrReferenceExpression)element)) { - addNodeAndCheckPending(new ReadWriteVariableInstruction(var.getVariableName(), typeDefinition, ReadWriteVariableInstruction.READ)); - } - } - addNodeAndCheckPending(new InstructionImpl(typeDefinition)); + addReadFromNestedControlFlow(typeDefinition, vars.toArray(ReadWriteVariableInstruction.EMPTY_ARRAY)); } private static Set collectUsedVariableWithoutInitialization(GrTypeDefinition typeDefinition) { diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyBlockImpl.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyBlockImpl.java deleted file mode 100644 index 6a46535833d4..000000000000 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyBlockImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package org.jetbrains.plugins.groovy.lang.psi.impl; - -import com.intellij.psi.tree.IElementType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor; -import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaBody; -import org.jetbrains.plugins.groovy.lang.psi.impl.statements.blocks.GrBlockImpl; - -public class GrLambdaBodyBlockImpl extends GrBlockImpl implements GrLambdaBody { - - public GrLambdaBodyBlockImpl(@NotNull IElementType type, CharSequence buffer) { - super(type, buffer); - } - - @Override - public void accept(@NotNull GroovyElementVisitor visitor) { - visitor.visitLambdaBody(this); - } - - @Override - public String toString() { - return "Lambda body block"; - } - - @Override - public boolean isTopControlFlowOwner() { - return true; - } -} diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyBlockImpl.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyBlockImpl.kt new file mode 100644 index 000000000000..734edaf89f4a --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyBlockImpl.kt @@ -0,0 +1,24 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.groovy.lang.psi.impl + +import com.intellij.psi.PsiType +import com.intellij.psi.tree.IElementType +import com.intellij.psi.util.parentOfType +import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor +import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaBody +import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaExpression +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.blocks.GrBlockImpl +import org.jetbrains.plugins.groovy.lang.resolve.MethodTypeInferencer + +class GrLambdaBodyBlockImpl(type: IElementType, buffer: CharSequence?) : GrBlockImpl(type, buffer), GrLambdaBody { + + override fun getReturnType(): PsiType? = GroovyPsiManager.inferType(this, MethodTypeInferencer(this)) + + override fun isTopControlFlowOwner(): Boolean = true + + override fun getLambdaExpression(): GrLambdaExpression = requireNotNull(parentOfType()) + + override fun accept(visitor: GroovyElementVisitor) = visitor.visitLambdaBody(this) + + override fun toString(): String = "Lambda body block" +} diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyExpressionImpl.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyExpressionImpl.kt index ecbd7bb0e78a..c62ba201472a 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyExpressionImpl.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaBodyExpressionImpl.kt @@ -2,15 +2,21 @@ package org.jetbrains.plugins.groovy.lang.psi.impl import com.intellij.lang.ASTNode +import com.intellij.psi.PsiType +import com.intellij.psi.util.parentOfType import com.intellij.util.IncorrectOperationException import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaBody +import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ControlFlowBuilder class GrLambdaBodyExpressionImpl(node: ASTNode) : GroovyPsiElementImpl(node), GrLambdaBody { + override fun getReturnType(): PsiType? = getExpression().type + + override fun getLambdaExpression(): GrLambdaExpression = requireNotNull(parentOfType()) fun getExpression() : GrExpression = findNotNullChildByClass(GrExpression::class.java) diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaExpressionImpl.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaExpressionImpl.kt index 0b8181ab650c..83aa63172f31 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaExpressionImpl.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrLambdaExpressionImpl.kt @@ -9,20 +9,19 @@ import com.intellij.psi.scope.PsiScopeProcessor import com.intellij.psi.util.CachedValueProvider.Result.create import com.intellij.psi.util.CachedValuesManager.getCachedValue import com.intellij.psi.util.PsiModificationTracker +import org.jetbrains.plugins.groovy.lang.psi.GroovyElementTypes import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaBody import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaExpression +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList -import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrExpressionImpl import org.jetbrains.plugins.groovy.lang.psi.impl.statements.params.GrParameterListImpl class GrLambdaExpressionImpl(node: ASTNode) : GrExpressionImpl(node), GrLambdaExpression { - override fun getParameters(): Array { - return parameterList.parameters - } + override fun getParameters(): Array = parameterList.parameters override fun getParameterList(): GrParameterList = findNotNullChildByClass(GrParameterListImpl::class.java) @@ -47,11 +46,13 @@ class GrLambdaExpressionImpl(node: ASTNode) : GrExpressionImpl(node), GrLambdaEx } } - override fun getReturnType(): PsiType? = TypeInferenceHelper.getCurrentContext().getExpressionType(this, ::calculateReturnType) + override fun getArrow(): PsiElement = findNotNullChildByType(GroovyElementTypes.T_ARROW) - override fun getType(): PsiType? { - return GrClosureType.create(this, true) - } + override fun getStatements(): Array = body?.statements ?: emptyArray() + + override fun getReturnType(): PsiType? = body?.returnType + + override fun getType(): PsiType? = GrClosureType.create(this, true) override fun toString(): String = "Lambda expression" } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrReassignedLocalVarsChecker.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrReassignedLocalVarsChecker.java index 0ba01613d703..d82bc9caeaee 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrReassignedLocalVarsChecker.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrReassignedLocalVarsChecker.java @@ -34,6 +34,7 @@ import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; +import org.jetbrains.plugins.groovy.lang.psi.api.GrLambdaBody; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock; @@ -88,6 +89,13 @@ public class GrReassignedLocalVarsChecker { } } + @Override + public void visitLambdaBody(@NotNull GrLambdaBody body) { + if (getUsedVarsInsideBlock(body).contains(name)) { + isReassigned.set(true); + } + } + @Override public void visitElement(@NotNull GroovyPsiElement element) { if (isReassigned.get()) return; @@ -144,11 +152,11 @@ public class GrReassignedLocalVarsChecker { } @NotNull - private static Set getUsedVarsInsideBlock(@NotNull final GrCodeBlock block) { - return CachedValuesManager.getCachedValue(block, () -> { + private static Set getUsedVarsInsideBlock(@NotNull final GroovyPsiElement element) { + return CachedValuesManager.getCachedValue(element, () -> { final Set result = ContainerUtil.newHashSet(); - block.acceptChildren(new GroovyRecursiveElementVisitor() { + element.acceptChildren(new GroovyRecursiveElementVisitor() { @Override public void visitOpenBlock(@NotNull GrOpenBlock openBlock) { @@ -160,6 +168,11 @@ public class GrReassignedLocalVarsChecker { result.addAll(getUsedVarsInsideBlock(closure)); } + @Override + public void visitLambdaBody(@NotNull GrLambdaBody body) { + result.addAll(getUsedVarsInsideBlock(body)); + } + @Override public void visitReferenceExpression(@NotNull GrReferenceExpression referenceExpression) { if (referenceExpression.getQualifier() == null && referenceExpression.getReferenceName() != null) { @@ -167,7 +180,7 @@ public class GrReassignedLocalVarsChecker { } } }); - return CachedValueProvider.Result.create(result, block); + return CachedValueProvider.Result.create(result, element); }); } From e73f8bb42a9e0c75e89e368199cdca170873f350 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Thu, 7 Feb 2019 11:54:08 +0300 Subject: [PATCH 2/5] IDEA-206611 Editor | General | SoftWraps| Soft-wrap file types: it is unclear how to specify types there --- .../options/editor/EditorOptionsPanel.form | 12 +++++++++--- .../options/editor/EditorOptionsPanel.java | 9 +++++++++ .../src/messages/ApplicationBundle.properties | 5 +++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.form b/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.form index dae5b21a8f3b..5fa718ac8fa6 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.form +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.form @@ -438,7 +438,7 @@ - + @@ -486,7 +486,7 @@ - + @@ -503,12 +503,18 @@ - + + + + + + + diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.java b/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.java index c98df9381cd0..038761f03004 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.java @@ -28,6 +28,7 @@ import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.options.ex.ConfigurableWrapper; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.ui.panel.ComponentPanelBuilder; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; @@ -39,6 +40,7 @@ import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBTextField; +import com.intellij.util.ui.JBEmptyBorder; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -99,6 +101,7 @@ public class EditorOptionsPanel extends CompositeConfigurableWhen tabs exceed the limit: group.soft.wraps=Soft Wraps -checkbox.use.soft.wraps.at.editor=Soft-wrap file types: -soft.wraps.file.masks.empty.text=No file types +checkbox.use.soft.wraps.at.editor=Soft-wrap files: +soft.wraps.file.masks.empty.text=No files +soft.wraps.file.masks.hint=Use ; to separate patterns, * and ? as wildcards checkbox.use.soft.wraps.at.console=Use soft wraps in console checkbox.use.custom.soft.wraps.indent=Use original line's indent for wrapped parts label.use.custom.soft.wraps.indent=Additional shift: From d1b59f3d1d058350358910f936b46e05f39bd92d Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 7 Feb 2019 10:06:35 +0100 Subject: [PATCH 3/5] [platform] removes obsolete EA response workaround (EXA-580) --- .../platform-impl/src/com/intellij/diagnostic/ITNProxy.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java b/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java index d3cbc3d49ba4..1e75598e5c59 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java @@ -175,9 +175,6 @@ class ITNProxy { HttpURLConnection connection = post(new URL(NEW_THREAD_POST_URL), createRequest(login, password, error)); int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_BAD_REQUEST && StringUtil.isEmpty(password)) { - throw new NoSuchEAPUserException(login); - } if (responseCode != HttpURLConnection.HTTP_OK) { throw new InternalEAPException(DiagnosticBundle.message("error.http.result.code", responseCode)); } From 164e3869e108710e1d221adec9c52e6a6b781610 Mon Sep 17 00:00:00 2001 From: Konstantin Ulitin Date: Thu, 7 Feb 2019 13:10:38 +0300 Subject: [PATCH 4/5] flow js: enable LSP by default --- platform/util/resources/misc/registry.properties | 4 ---- 1 file changed, 4 deletions(-) diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 3ae906195a47..fdf9e3f5e7b5 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -1579,10 +1579,6 @@ typescript.service.node.arguments= typescript.service.node.arguments.description=Node options to use when starting TypeScript service process javascript.exclude.source.if.package.has.typings=true javascript.exclude.source.if.package.has.typings.description=Don't index JavaScript files in a node module if it has type declaration files -javascript.flow.lazy.mode=false -javascript.flow.lazy.mode.description=Passes '--lazy-mode fs' to flow server command -javascript.flow.use.lsp=false -javascript.flow.use.lsp.description=Enables using Language Server Protocol for communication with Flow javascript.linters.prevent.detection=false javascript.linters.prevent.enable.on.project.opening.description=Disables JS linter detection on project opening From 043c2d563e2bd4d41e30bcfe62922c96ad382b35 Mon Sep 17 00:00:00 2001 From: Artemiy Sartakov Date: Thu, 7 Feb 2019 17:25:25 +0700 Subject: [PATCH 5/5] Field can be local: added support for nested and local classes (IDEA-206647) --- .../FieldCanBeLocalInspection.java | 46 +++++++++++++++++-- .../convert2Local/afterLocalClass.java | 12 +++++ .../afterLocalClassMultipleMethods.java | 19 ++++++++ .../convert2Local/afterNestedClass.java | 10 ++++ .../afterOtherFieldInitilizer.java | 7 +++ ...oreInnerFieldReferencedFromOuterClass.java | 15 ++++++ .../convert2Local/beforeLocalClass.java | 13 ++++++ .../beforeLocalClassMultipleMethods.java | 20 ++++++++ .../convert2Local/beforeNestedClass.java | 11 +++++ .../beforeOtherFieldInitilizer.java | 9 ++++ .../InnerClassConstructor.java | 2 +- .../fieldCanBeLocal/LocalVar2InnerClass.java | 2 +- 12 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterLocalClass.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterLocalClassMultipleMethods.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterNestedClass.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterOtherFieldInitilizer.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeInnerFieldReferencedFromOuterClass.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeLocalClass.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeLocalClassMultipleMethods.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeNestedClass.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeOtherFieldInitilizer.java diff --git a/java/java-impl/src/com/intellij/codeInspection/varScopeCanBeNarrowed/FieldCanBeLocalInspection.java b/java/java-impl/src/com/intellij/codeInspection/varScopeCanBeNarrowed/FieldCanBeLocalInspection.java index d5c54e5ed308..e772a86bcd96 100644 --- a/java/java-impl/src/com/intellij/codeInspection/varScopeCanBeNarrowed/FieldCanBeLocalInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/varScopeCanBeNarrowed/FieldCanBeLocalInspection.java @@ -69,10 +69,13 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo if (candidates.isEmpty()) return; final List implicitUsageProviders = ImplicitUsageProvider.EP_NAME.getExtensionList(); + PsiClass scope = PsiTreeUtil.getTopmostParentOfType(aClass, PsiClass.class); + if (scope == null) scope = aClass; + FieldLoop: for (final PsiField field : candidates) { if (usedFields.contains(field) && !hasImplicitReadOrWriteUsage(field, implicitUsageProviders)) { - final Query references = ReferencesSearch.search(field, new LocalSearchScope(aClass)); + final Query references = ReferencesSearch.search(field, new LocalSearchScope(scope)); final Map> refs = new HashMap<>(); for (PsiReference reference : references.findAll()) { final PsiElement element = reference.getElement(); @@ -151,6 +154,11 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo //do not go inside class initializer } + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + // do not go inside lambda + } + @Override public void visitReferenceExpression(PsiReferenceExpression expression) { excludeFieldCandidate(expression); @@ -295,7 +303,7 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo private static boolean groupReferenceByCodeBlocks(Map> refs, PsiReference psiReference) { final PsiElement element = psiReference.getElement(); - final PsiCodeBlock block = PsiTreeUtil.getTopmostParentOfType(element, PsiCodeBlock.class); + final PsiCodeBlock block = getTopmostBlock(element); if (block == null) { return false; } @@ -310,6 +318,17 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo return true; } + @Nullable + private static PsiCodeBlock getTopmostBlock(@NotNull PsiElement element) { + PsiElement parent = element.getParent(); + PsiCodeBlock block = null; + while (parent != null && !(parent instanceof PsiClass)) { + if (parent instanceof PsiCodeBlock) block = (PsiCodeBlock)parent; + parent = parent.getParent(); + } + return block; + } + private static boolean findExistentBlock(Map> refs, PsiReference psiReference, PsiCodeBlock block, @@ -365,8 +384,29 @@ public class FieldCanBeLocalInspection extends AbstractBaseJavaLocalInspectionTo return new JavaElementVisitor() { @Override public void visitJavaFile(PsiJavaFile file) { + final JavaElementVisitor visitor = new JavaElementVisitor() { + @Override + public void visitClass(PsiClass aClass) { + super.visitClass(aClass); + Arrays.stream(aClass.getChildren()).forEach(c -> c.accept(this)); + doCheckClass(aClass, holder, EXCLUDE_ANNOS, IGNORE_FIELDS_USED_IN_MULTIPLE_METHODS); + } + + @Override + public void visitDeclarationStatement(PsiDeclarationStatement statement) { + super.visitDeclarationStatement(statement); + Arrays.stream(statement.getDeclaredElements()).forEach(d -> d.accept(this)); + } + + @Override + public void visitMethod(PsiMethod method) { + super.visitMethod(method); + final PsiCodeBlock body = method.getBody(); + if (body != null) Arrays.stream(body.getChildren()).forEach(c -> c.accept(this)); + } + }; for (PsiClass aClass : file.getClasses()) { - doCheckClass(aClass, holder, EXCLUDE_ANNOS, IGNORE_FIELDS_USED_IN_MULTIPLE_METHODS); + aClass.accept(visitor); } } }; diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterLocalClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterLocalClass.java new file mode 100644 index 000000000000..eba3646182e3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterLocalClass.java @@ -0,0 +1,12 @@ +// "Convert field to local variable in method 'test'" "true" +class Foo { + void test() { + class Bar { + + void test() { + int x = 2; // could be local + System.out.println(x); + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterLocalClassMultipleMethods.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterLocalClassMultipleMethods.java new file mode 100644 index 000000000000..92ffb51f05b7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterLocalClassMultipleMethods.java @@ -0,0 +1,19 @@ +// "Convert to local" "true" +class Outer { + + void test() { + class Local { + + void foo() { + String s = "1"; + System.out.println(s); + } + + void bar() { + String s = "2"; + System.out.println(s); + } + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterNestedClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterNestedClass.java new file mode 100644 index 000000000000..890320820fba --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterNestedClass.java @@ -0,0 +1,10 @@ +// "Convert field to local variable in method 'test'" "true" +class Foo { + static class Bar { + + void test() { + int x = 2; // could be local + System.out.println(x); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterOtherFieldInitilizer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterOtherFieldInitilizer.java new file mode 100644 index 000000000000..ddd097b9cfa1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/afterOtherFieldInitilizer.java @@ -0,0 +1,7 @@ +// "Convert to local" "true" +class Test { + + private Runnable r = () -> { + String field = "foo"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeInnerFieldReferencedFromOuterClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeInnerFieldReferencedFromOuterClass.java new file mode 100644 index 000000000000..c671cc55713d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeInnerFieldReferencedFromOuterClass.java @@ -0,0 +1,15 @@ +// "Fix all 'Field can be local' problems in file" "false" +class Outer { + + void test(Inner inner) { + System.out.println(inner.field); + } + + class Inner { + private final String field; + + Inner(String field) { + this.field = field; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeLocalClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeLocalClass.java new file mode 100644 index 000000000000..a23510af07bc --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeLocalClass.java @@ -0,0 +1,13 @@ +// "Convert field to local variable in method 'test'" "true" +class Foo { + void test() { + class Bar { + private int x; + + void test() { + x = 2; // could be local + System.out.println(x); + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeLocalClassMultipleMethods.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeLocalClassMultipleMethods.java new file mode 100644 index 000000000000..88a1f5ec0457 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeLocalClassMultipleMethods.java @@ -0,0 +1,20 @@ +// "Convert to local" "true" +class Outer { + + void test() { + class Local { + private String s; + + void foo() { + s = "1"; + System.out.println(s); + } + + void bar() { + s = "2"; + System.out.println(s); + } + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeNestedClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeNestedClass.java new file mode 100644 index 000000000000..5d288148b393 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeNestedClass.java @@ -0,0 +1,11 @@ +// "Convert field to local variable in method 'test'" "true" +class Foo { + static class Bar { + private int x; + + void test() { + x = 2; // could be local + System.out.println(x); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeOtherFieldInitilizer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeOtherFieldInitilizer.java new file mode 100644 index 000000000000..ab16b7d65e5a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convert2Local/beforeOtherFieldInitilizer.java @@ -0,0 +1,9 @@ +// "Convert to local" "true" +class Test { + + private String field; + + private Runnable r = () -> { + field = "foo"; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/fieldCanBeLocal/InnerClassConstructor.java b/java/java-tests/testData/inspection/fieldCanBeLocal/InnerClassConstructor.java index 23c7c27d5476..db8a4131402a 100644 --- a/java/java-tests/testData/inspection/fieldCanBeLocal/InnerClassConstructor.java +++ b/java/java-tests/testData/inspection/fieldCanBeLocal/InnerClassConstructor.java @@ -2,7 +2,7 @@ private int value = 0; public class Inner { - private final int myValue; + private final int myValue; public Inner() { myValue = value++; diff --git a/java/java-tests/testData/inspection/fieldCanBeLocal/LocalVar2InnerClass.java b/java/java-tests/testData/inspection/fieldCanBeLocal/LocalVar2InnerClass.java index 787dc7127f3a..6256aa659df4 100644 --- a/java/java-tests/testData/inspection/fieldCanBeLocal/LocalVar2InnerClass.java +++ b/java/java-tests/testData/inspection/fieldCanBeLocal/LocalVar2InnerClass.java @@ -2,7 +2,7 @@ private int value = 0; public class Inner { - private final int myValue; + private final int myValue; public Inner() { myValue = value;