/* * 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(@Nullable 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 = unwrapParensAndCast(GoPsiImplUtil.getRightExpression(assignment, getTopmostExpression(referenceExpression))); 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)); } @NotNull private static GoExpression getTopmostExpression(@NotNull GoExpression expression) { return ObjectUtils.notNull(PsiTreeUtil.getTopmostParentOfType(expression, GoExpression.class), expression); } private static boolean isResolvedTo(@Nullable PsiElement e, @Nullable PsiElement resolve) { if (e instanceof GoVarDefinition) return resolve == e; GoReferenceExpression refExpression = unwrapParensAndCast(e); 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 anchor = getTopmostExpression(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; } } }