Skip to content

Rework move to struct assignment intention by wbars #2806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions resources/META-INF/gogland.xml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@
<categoryKey>go.intentions.category</categoryKey>
<className>com.goide.intentions.GoAddFunctionBlockIntention</className>
</intentionAction>
<intentionAction>
<bundleName>com.goide.GoBundle</bundleName>
<categoryKey>go.intentions.category</categoryKey>
<className>com.goide.intentions.GoMoveToStructInitializationIntention</className>
</intentionAction>
<!-- unused inspections -->
<localInspection language="go" displayName="Unused import inspection" groupPath="Go"
groupName="Declaration redundancy" enabledByDefault="true" level="ERROR"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s := S{<spot>foo: `bar`</spot>}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
s := S{}
<spot>s.foo = `bar`</spot>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!--
~ Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
~
~ 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.
-->

<html>
<body>
This intention moves struct field assignment to struct initialization block.
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public GoReplaceWithNamedStructFieldQuickFix(@NotNull String structField) {
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement startElement = descriptor.getStartElement();
if (startElement instanceof GoElement) {
startElement.replace(GoElementFactory.createNamedStructField(project, myStructField, startElement.getText()));
startElement.replace(GoElementFactory.createLiteralValueElement(project, myStructField, startElement.getText()));
}
}
}
Expand Down
292 changes: 292 additions & 0 deletions src/com/goide/intentions/GoMoveToStructInitializationIntention.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* 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.goide.intentions;

import com.goide.psi.*;
import com.goide.psi.impl.GoElementFactory;
import com.goide.psi.impl.GoPsiImplUtil;
import com.intellij.codeInsight.intention.BaseElementAtCaretIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Set;

import static com.intellij.util.containers.ContainerUtil.*;

public class GoMoveToStructInitializationIntention extends BaseElementAtCaretIntentionAction {
public static final String NAME = "Move field assignment to struct initialization";

public GoMoveToStructInitializationIntention() {
setText(NAME);
}

@Nls
@NotNull
@Override
public String getFamilyName() {
return NAME;
}

@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
return getData(element) != null;
}

@Nullable
private static Data getData(@NotNull PsiElement element) {
if (!element.isValid() || !element.isWritable()) return null;
GoAssignmentStatement assignment = getValidAssignmentParent(element);
GoReferenceExpression selectedFieldReference = assignment != null ? getFieldReferenceExpression(element, assignment) : null;
GoCompositeLit compositeLit = selectedFieldReference != null ? getStructLiteralByReference(selectedFieldReference, assignment) : null;
if (compositeLit == null) return null;

List<GoReferenceExpression> references = getUninitializedSingleFieldReferences(assignment, selectedFieldReference, compositeLit);
return !references.isEmpty() ? new Data(assignment, compositeLit, references) : null;
}

@Nullable
private static GoAssignmentStatement getValidAssignmentParent(@Nullable PsiElement element) {
GoAssignmentStatement assignment = PsiTreeUtil.getNonStrictParentOfType(element, GoAssignmentStatement.class);
return assignment != null && assignment.isValid() && getLeftHandElements(assignment).size() == assignment.getExpressionList().size()
? assignment : null;
}

@Nullable
private static GoReferenceExpression getFieldReferenceExpression(@NotNull PsiElement selectedElement,
@NotNull GoAssignmentStatement assignment) {
GoReferenceExpression selectedReferenceExpression = PsiTreeUtil.getTopmostParentOfType(selectedElement, GoReferenceExpression.class);
if (isFieldReferenceExpression(selectedReferenceExpression)) {
return !isAssignedInPreviousStatement(selectedReferenceExpression, assignment) ? selectedReferenceExpression : null;
}

List<GoReferenceExpression> fieldReferenceExpressions = getFieldReferenceExpressions(assignment);
if (exists(fieldReferenceExpressions, expression -> isAssignedInPreviousStatement(expression, assignment))) return null;

Set<PsiElement> resolvedFields = map2Set(fieldReferenceExpressions, GoMoveToStructInitializationIntention::resolveQualifier);
return resolvedFields.size() == 1 ? getFirstItem(fieldReferenceExpressions) : null;
}

@NotNull
private static List<GoReferenceExpression> getFieldReferenceExpressions(@NotNull GoAssignmentStatement assignment) {
return filter(map(getLeftHandElements(assignment), GoMoveToStructInitializationIntention::unwrapParensAndCast),
GoMoveToStructInitializationIntention::isFieldReferenceExpression);
}

@Nullable
private static GoReferenceExpression unwrapParensAndCast(@NotNull PsiElement e) {
while (e instanceof GoParenthesesExpr) {
e = ((GoParenthesesExpr)e).getExpression();
}
return ObjectUtils.tryCast(e, GoReferenceExpression.class);
}

@Contract("null -> false")
private static boolean isFieldReferenceExpression(@Nullable PsiElement element) {
return element instanceof GoReferenceExpression && isFieldDefinition(((GoReferenceExpression)element).resolve());
}

@Contract("null -> false")
private static boolean isFieldDefinition(@Nullable PsiElement element) {
return element instanceof GoFieldDefinition || element instanceof GoAnonymousFieldDefinition;
}

private static boolean isAssignedInPreviousStatement(@NotNull GoExpression referenceExpression,
@NotNull GoAssignmentStatement assignment) {
GoReferenceExpression rightExpression = ObjectUtils.tryCast(GoPsiImplUtil.getRightExpression(assignment, referenceExpression),
GoReferenceExpression.class);

PsiElement resolve = rightExpression != null ? rightExpression.resolve() : null;
GoStatement previousElement = resolve != null ? PsiTreeUtil.getPrevSiblingOfType(assignment, GoStatement.class) : null;
return previousElement != null && exists(getLeftHandElements(previousElement), e -> isResolvedTo(e, resolve));
}

private static boolean isResolvedTo(@Nullable PsiElement e, @Nullable PsiElement resolve) {
if (e instanceof GoVarDefinition) return resolve == e;

GoReferenceExpression refExpression = ObjectUtils.tryCast(e, GoReferenceExpression.class);
return refExpression != null && refExpression.resolve() == resolve;
}

@NotNull
private static List<GoReferenceExpression> getUninitializedSingleFieldReferences(@NotNull GoAssignmentStatement assignment,
@NotNull GoReferenceExpression fieldReferenceExpression,
@NotNull GoCompositeLit compositeLit) {
PsiElement resolve = resolveQualifier(fieldReferenceExpression);
List<GoReferenceExpression> uninitializedFieldReferencesByQualifier =
filter(getUninitializedFieldReferenceExpressions(assignment, compositeLit), e -> isResolvedTo(e.getQualifier(), resolve));
MultiMap<PsiElement, GoReferenceExpression> resolved = groupBy(uninitializedFieldReferencesByQualifier, GoReferenceExpression::resolve);
return map(filter(resolved.entrySet(), set -> set.getValue().size() == 1), set -> getFirstItem(set.getValue()));
}

@Nullable
private static GoCompositeLit getStructLiteralByReference(@NotNull GoReferenceExpression fieldReferenceExpression,
@NotNull GoAssignmentStatement assignment) {
GoStatement previousStatement = PsiTreeUtil.getPrevSiblingOfType(assignment, GoStatement.class);
if (previousStatement instanceof GoSimpleStatement) {
return getStructLiteral(fieldReferenceExpression, (GoSimpleStatement)previousStatement);
}
if (previousStatement instanceof GoAssignmentStatement) {
return getStructLiteral(fieldReferenceExpression, (GoAssignmentStatement)previousStatement);
}
return null;
}

@Nullable
private static GoCompositeLit getStructLiteral(@NotNull GoReferenceExpression fieldReferenceExpression,
@NotNull GoSimpleStatement structDeclaration) {
GoShortVarDeclaration varDeclaration = structDeclaration.getShortVarDeclaration();
if (varDeclaration == null) return null;

PsiElement resolve = resolveQualifier(fieldReferenceExpression);
GoVarDefinition structVarDefinition = find(varDeclaration.getVarDefinitionList(), definition -> resolve == definition);
return structVarDefinition != null ? ObjectUtils.tryCast(structVarDefinition.getValue(), GoCompositeLit.class) : null;
}

@Nullable
private static PsiElement resolveQualifier(@NotNull GoReferenceExpression fieldReferenceExpression) {
GoReferenceExpression qualifier = fieldReferenceExpression.getQualifier();
return qualifier != null ? qualifier.resolve() : null;
}

@Nullable
private static GoCompositeLit getStructLiteral(@NotNull GoReferenceExpression fieldReferenceExpression,
@NotNull GoAssignmentStatement structAssignment) {
GoVarDefinition varDefinition = ObjectUtils.tryCast(resolveQualifier(fieldReferenceExpression), GoVarDefinition.class);
PsiElement field = fieldReferenceExpression.resolve();
if (varDefinition == null || !isFieldDefinition(field) || !hasStructTypeWithField(varDefinition, (GoNamedElement)field)) {
return null;
}

GoExpression structReferenceExpression = find(structAssignment.getLeftHandExprList().getExpressionList(),
expression -> isResolvedTo(expression, varDefinition));
if (structReferenceExpression == null) return null;
GoExpression compositeLit = GoPsiImplUtil.getRightExpression(structAssignment, structReferenceExpression);
return ObjectUtils.tryCast(compositeLit, GoCompositeLit.class);
}

private static boolean hasStructTypeWithField(@NotNull GoVarDefinition structVarDefinition, @NotNull GoNamedElement field) {
GoType type = structVarDefinition.getGoType(null);
GoStructType structType = type != null ? ObjectUtils.tryCast(type.getUnderlyingType(), GoStructType.class) : null;
return structType != null && PsiTreeUtil.isAncestor(structType, field, true);
}

private static boolean isFieldInitialization(@NotNull GoElement element, @NotNull PsiElement field) {
GoKey key = element.getKey();
GoFieldName fieldName = key != null ? key.getFieldName() : null;
return fieldName != null && fieldName.resolve() == field;
}

@NotNull
private static List<GoReferenceExpression> getUninitializedFieldReferenceExpressions(@NotNull GoAssignmentStatement assignment,
@NotNull GoCompositeLit structLiteral) {
return filter(getFieldReferenceExpressions(assignment), expression ->
isUninitializedFieldReferenceExpression(expression, structLiteral) && !isAssignedInPreviousStatement(expression, assignment));
}

@Contract("null, _-> false")
private static boolean isUninitializedFieldReferenceExpression(@Nullable GoReferenceExpression fieldReferenceExpression,
@NotNull GoCompositeLit structLiteral) {
if (fieldReferenceExpression == null) return false;
GoLiteralValue literalValue = structLiteral.getLiteralValue();
PsiElement resolve = fieldReferenceExpression.resolve();
return literalValue != null && isFieldDefinition(resolve) &&
!exists(literalValue.getElementList(), element -> isFieldInitialization(element, resolve));
}

@NotNull
private static List<? extends PsiElement> getLeftHandElements(@NotNull GoStatement statement) {
if (statement instanceof GoSimpleStatement) {
GoShortVarDeclaration varDeclaration = ((GoSimpleStatement)statement).getShortVarDeclaration();
return varDeclaration != null ? varDeclaration.getVarDefinitionList() : emptyList();
}
if (statement instanceof GoAssignmentStatement) {
return ((GoAssignmentStatement)statement).getLeftHandExprList().getExpressionList();
}
return emptyList();
}

@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
Data data = getData(element);
if (data == null) return;
moveFieldReferenceExpressions(data);
}

private static void moveFieldReferenceExpressions(@NotNull Data data) {
GoLiteralValue literalValue = data.getCompositeLit().getLiteralValue();
if (literalValue == null) return;

for (GoReferenceExpression expression : data.getReferenceExpressions()) {
GoExpression parentExpression = PsiTreeUtil.getTopmostParentOfType(expression, GoExpression.class);
GoExpression anchor = parentExpression != null ? parentExpression : expression;
GoExpression fieldValue = GoPsiImplUtil.getRightExpression(data.getAssignment(), anchor);
if (fieldValue == null) continue;

GoPsiImplUtil.deleteExpressionFromAssignment(data.getAssignment(), anchor);
addFieldDefinition(literalValue, expression.getIdentifier().getText(), fieldValue.getText());
}
}

private static void addFieldDefinition(@NotNull GoLiteralValue literalValue, @NotNull String name, @NotNull String value) {
Project project = literalValue.getProject();
PsiElement newField = GoElementFactory.createLiteralValueElement(project, name, value);
GoElement lastElement = getLastItem(literalValue.getElementList());
if (lastElement == null) {
literalValue.addAfter(newField, literalValue.getLbrace());
}
else {
lastElement.add(GoElementFactory.createComma(project));
lastElement.add(newField);
}
}

private static class Data {
private final GoCompositeLit myCompositeLit;
private final GoAssignmentStatement myAssignment;
private final List<GoReferenceExpression> myReferenceExpressions;

public Data(@NotNull GoAssignmentStatement assignment,
@NotNull GoCompositeLit compositeLit,
@NotNull List<GoReferenceExpression> referenceExpressions) {
myCompositeLit = compositeLit;
myAssignment = assignment;
myReferenceExpressions = referenceExpressions;
}

public GoCompositeLit getCompositeLit() {
return myCompositeLit;
}

public GoAssignmentStatement getAssignment() {
return myAssignment;
}

public List<GoReferenceExpression> getReferenceExpressions() {
return myReferenceExpressions;
}
}
}
4 changes: 2 additions & 2 deletions src/com/goide/psi/impl/GoElementFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ public static GoType createType(@NotNull Project project, @NotNull String text)
return PsiTreeUtil.findChildOfType(file, GoType.class);
}

public static PsiElement createNamedStructField(@NotNull Project project, @NotNull String field, @NotNull String element) {
GoFile file = createFileFromText(project, "package a; var _ = struct { a string } { " + field + ": " + element + " }");
public static PsiElement createLiteralValueElement(@NotNull Project project, @NotNull String key, @NotNull String value) {
GoFile file = createFileFromText(project, "package a; var _ = struct { a string } { " + key + ": " + value + " }");
return PsiTreeUtil.findChildOfType(file, GoElement.class);
}

Expand Down
20 changes: 20 additions & 0 deletions src/com/goide/psi/impl/GoPsiImplUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,20 @@ public static void deleteSpec(@NotNull GoConstDeclaration declaration, @NotNull
specToDelete.delete();
}

public static void deleteExpressionFromAssignment(@NotNull GoAssignmentStatement assignment,
@NotNull GoExpression expressionToDelete) {
GoExpression expressionValue = getRightExpression(assignment, expressionToDelete);
if (expressionValue != null) {
if (assignment.getExpressionList().size() == 1) {
assignment.delete();
}
else {
deleteElementFromCommaSeparatedList(expressionToDelete);
deleteElementFromCommaSeparatedList(expressionValue);
}
}
}

public static void deleteDefinition(@NotNull GoVarSpec spec, @NotNull GoVarDefinition definitionToDelete) {
List<GoVarDefinition> definitionList = spec.getVarDefinitionList();
int index = definitionList.indexOf(definitionToDelete);
Expand Down Expand Up @@ -1679,4 +1693,10 @@ private static PsiElement getNotNullElement(@Nullable PsiElement... elements) {
public static boolean isSingleCharLiteral(@NotNull GoStringLiteral literal) {
return literal.getDecodedText().length() == 1;
}

@Nullable
public static GoExpression getRightExpression(@NotNull GoAssignmentStatement assignment, @NotNull GoExpression leftExpression) {
int fieldIndex = assignment.getLeftHandExprList().getExpressionList().indexOf(leftExpression);
return getByIndex(assignment.getExpressionList(), fieldIndex);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

type S struct {
string
}

func main() {
s := S{string: "bar"}
_ = "foo"
print(s.string)
}
Loading