|
| 1 | +/* |
| 2 | + * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package com.goide.intentions; |
| 18 | + |
| 19 | +import com.goide.psi.*; |
| 20 | +import com.goide.psi.impl.GoElementFactory; |
| 21 | +import com.goide.psi.impl.GoPsiImplUtil; |
| 22 | +import com.intellij.codeInsight.intention.BaseElementAtCaretIntentionAction; |
| 23 | +import com.intellij.openapi.editor.Editor; |
| 24 | +import com.intellij.openapi.project.Project; |
| 25 | +import com.intellij.psi.PsiElement; |
| 26 | +import com.intellij.psi.util.PsiTreeUtil; |
| 27 | +import com.intellij.util.IncorrectOperationException; |
| 28 | +import com.intellij.util.ObjectUtils; |
| 29 | +import com.intellij.util.containers.MultiMap; |
| 30 | +import org.jetbrains.annotations.Contract; |
| 31 | +import org.jetbrains.annotations.Nls; |
| 32 | +import org.jetbrains.annotations.NotNull; |
| 33 | +import org.jetbrains.annotations.Nullable; |
| 34 | + |
| 35 | +import java.util.List; |
| 36 | +import java.util.Set; |
| 37 | + |
| 38 | +import static com.intellij.util.containers.ContainerUtil.*; |
| 39 | + |
| 40 | +public class GoMoveToStructInitializationIntention extends BaseElementAtCaretIntentionAction { |
| 41 | + public static final String NAME = "Move field assignment to struct initialization"; |
| 42 | + |
| 43 | + public GoMoveToStructInitializationIntention() { |
| 44 | + setText(NAME); |
| 45 | + } |
| 46 | + |
| 47 | + @Nls |
| 48 | + @NotNull |
| 49 | + @Override |
| 50 | + public String getFamilyName() { |
| 51 | + return NAME; |
| 52 | + } |
| 53 | + |
| 54 | + @Override |
| 55 | + public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { |
| 56 | + return getData(element) != null; |
| 57 | + } |
| 58 | + |
| 59 | + @Nullable |
| 60 | + private static Data getData(@NotNull PsiElement element) { |
| 61 | + if (!element.isValid() || !element.isWritable()) return null; |
| 62 | + GoAssignmentStatement assignment = getValidAssignmentParent(element); |
| 63 | + GoReferenceExpression selectedFieldReference = assignment != null ? getFieldReferenceExpression(element, assignment) : null; |
| 64 | + GoCompositeLit compositeLit = selectedFieldReference != null ? getStructLiteralByReference(selectedFieldReference, assignment) : null; |
| 65 | + if (compositeLit == null) return null; |
| 66 | + |
| 67 | + List<GoReferenceExpression> references = getUninitializedSingleFieldReferences(assignment, selectedFieldReference, compositeLit); |
| 68 | + return !references.isEmpty() ? new Data(assignment, compositeLit, references) : null; |
| 69 | + } |
| 70 | + |
| 71 | + @Nullable |
| 72 | + private static GoAssignmentStatement getValidAssignmentParent(@Nullable PsiElement element) { |
| 73 | + GoAssignmentStatement assignment = PsiTreeUtil.getNonStrictParentOfType(element, GoAssignmentStatement.class); |
| 74 | + return assignment != null && assignment.isValid() && getLeftHandElements(assignment).size() == assignment.getExpressionList().size() |
| 75 | + ? assignment : null; |
| 76 | + } |
| 77 | + |
| 78 | + @Nullable |
| 79 | + private static GoReferenceExpression getFieldReferenceExpression(@NotNull PsiElement selectedElement, |
| 80 | + @NotNull GoAssignmentStatement assignment) { |
| 81 | + GoReferenceExpression selectedReferenceExpression = PsiTreeUtil.getTopmostParentOfType(selectedElement, GoReferenceExpression.class); |
| 82 | + if (isFieldReferenceExpression(selectedReferenceExpression)) { |
| 83 | + return !isAssignedInPreviousStatement(selectedReferenceExpression, assignment) ? selectedReferenceExpression : null; |
| 84 | + } |
| 85 | + |
| 86 | + List<GoReferenceExpression> fieldReferenceExpressions = getFieldReferenceExpressions(assignment); |
| 87 | + if (exists(fieldReferenceExpressions, expression -> isAssignedInPreviousStatement(expression, assignment))) return null; |
| 88 | + |
| 89 | + Set<PsiElement> resolvedFields = map2Set(fieldReferenceExpressions, GoMoveToStructInitializationIntention::resolveQualifier); |
| 90 | + return resolvedFields.size() == 1 ? getFirstItem(fieldReferenceExpressions) : null; |
| 91 | + } |
| 92 | + |
| 93 | + @NotNull |
| 94 | + private static List<GoReferenceExpression> getFieldReferenceExpressions(@NotNull GoAssignmentStatement assignment) { |
| 95 | + return filter(map(getLeftHandElements(assignment), GoMoveToStructInitializationIntention::unwrapParensAndCast), |
| 96 | + GoMoveToStructInitializationIntention::isFieldReferenceExpression); |
| 97 | + } |
| 98 | + |
| 99 | + @Nullable |
| 100 | + private static GoReferenceExpression unwrapParensAndCast(@NotNull PsiElement e) { |
| 101 | + while (e instanceof GoParenthesesExpr) { |
| 102 | + e = ((GoParenthesesExpr)e).getExpression(); |
| 103 | + } |
| 104 | + return ObjectUtils.tryCast(e, GoReferenceExpression.class); |
| 105 | + } |
| 106 | + |
| 107 | + @Contract("null -> false") |
| 108 | + private static boolean isFieldReferenceExpression(@Nullable PsiElement element) { |
| 109 | + return element instanceof GoReferenceExpression && isFieldDefinition(((GoReferenceExpression)element).resolve()); |
| 110 | + } |
| 111 | + |
| 112 | + @Contract("null -> false") |
| 113 | + private static boolean isFieldDefinition(@Nullable PsiElement element) { |
| 114 | + return element instanceof GoFieldDefinition || element instanceof GoAnonymousFieldDefinition; |
| 115 | + } |
| 116 | + |
| 117 | + private static boolean isAssignedInPreviousStatement(@NotNull GoExpression referenceExpression, |
| 118 | + @NotNull GoAssignmentStatement assignment) { |
| 119 | + GoReferenceExpression rightExpression = ObjectUtils.tryCast(GoPsiImplUtil.getRightExpression(assignment, referenceExpression), |
| 120 | + GoReferenceExpression.class); |
| 121 | + |
| 122 | + PsiElement resolve = rightExpression != null ? rightExpression.resolve() : null; |
| 123 | + GoStatement previousElement = resolve != null ? PsiTreeUtil.getPrevSiblingOfType(assignment, GoStatement.class) : null; |
| 124 | + return previousElement != null && exists(getLeftHandElements(previousElement), e -> isResolvedTo(e, resolve)); |
| 125 | + } |
| 126 | + |
| 127 | + private static boolean isResolvedTo(@Nullable PsiElement e, @Nullable PsiElement resolve) { |
| 128 | + if (e instanceof GoVarDefinition) return resolve == e; |
| 129 | + |
| 130 | + GoReferenceExpression refExpression = ObjectUtils.tryCast(e, GoReferenceExpression.class); |
| 131 | + return refExpression != null && refExpression.resolve() == resolve; |
| 132 | + } |
| 133 | + |
| 134 | + @NotNull |
| 135 | + private static List<GoReferenceExpression> getUninitializedSingleFieldReferences(@NotNull GoAssignmentStatement assignment, |
| 136 | + @NotNull GoReferenceExpression fieldReferenceExpression, |
| 137 | + @NotNull GoCompositeLit compositeLit) { |
| 138 | + PsiElement resolve = resolveQualifier(fieldReferenceExpression); |
| 139 | + List<GoReferenceExpression> uninitializedFieldReferencesByQualifier = |
| 140 | + filter(getUninitializedFieldReferenceExpressions(assignment, compositeLit), e -> isResolvedTo(e.getQualifier(), resolve)); |
| 141 | + MultiMap<PsiElement, GoReferenceExpression> resolved = groupBy(uninitializedFieldReferencesByQualifier, GoReferenceExpression::resolve); |
| 142 | + return map(filter(resolved.entrySet(), set -> set.getValue().size() == 1), set -> getFirstItem(set.getValue())); |
| 143 | + } |
| 144 | + |
| 145 | + @Nullable |
| 146 | + private static GoCompositeLit getStructLiteralByReference(@NotNull GoReferenceExpression fieldReferenceExpression, |
| 147 | + @NotNull GoAssignmentStatement assignment) { |
| 148 | + GoStatement previousStatement = PsiTreeUtil.getPrevSiblingOfType(assignment, GoStatement.class); |
| 149 | + if (previousStatement instanceof GoSimpleStatement) { |
| 150 | + return getStructLiteral(fieldReferenceExpression, (GoSimpleStatement)previousStatement); |
| 151 | + } |
| 152 | + if (previousStatement instanceof GoAssignmentStatement) { |
| 153 | + return getStructLiteral(fieldReferenceExpression, (GoAssignmentStatement)previousStatement); |
| 154 | + } |
| 155 | + return null; |
| 156 | + } |
| 157 | + |
| 158 | + @Nullable |
| 159 | + private static GoCompositeLit getStructLiteral(@NotNull GoReferenceExpression fieldReferenceExpression, |
| 160 | + @NotNull GoSimpleStatement structDeclaration) { |
| 161 | + GoShortVarDeclaration varDeclaration = structDeclaration.getShortVarDeclaration(); |
| 162 | + if (varDeclaration == null) return null; |
| 163 | + |
| 164 | + PsiElement resolve = resolveQualifier(fieldReferenceExpression); |
| 165 | + GoVarDefinition structVarDefinition = find(varDeclaration.getVarDefinitionList(), definition -> resolve == definition); |
| 166 | + return structVarDefinition != null ? ObjectUtils.tryCast(structVarDefinition.getValue(), GoCompositeLit.class) : null; |
| 167 | + } |
| 168 | + |
| 169 | + @Nullable |
| 170 | + private static PsiElement resolveQualifier(@NotNull GoReferenceExpression fieldReferenceExpression) { |
| 171 | + GoReferenceExpression qualifier = fieldReferenceExpression.getQualifier(); |
| 172 | + return qualifier != null ? qualifier.resolve() : null; |
| 173 | + } |
| 174 | + |
| 175 | + @Nullable |
| 176 | + private static GoCompositeLit getStructLiteral(@NotNull GoReferenceExpression fieldReferenceExpression, |
| 177 | + @NotNull GoAssignmentStatement structAssignment) { |
| 178 | + GoVarDefinition varDefinition = ObjectUtils.tryCast(resolveQualifier(fieldReferenceExpression), GoVarDefinition.class); |
| 179 | + PsiElement field = fieldReferenceExpression.resolve(); |
| 180 | + if (varDefinition == null || !isFieldDefinition(field) || !hasStructTypeWithField(varDefinition, (GoNamedElement)field)) { |
| 181 | + return null; |
| 182 | + } |
| 183 | + |
| 184 | + GoExpression structReferenceExpression = find(structAssignment.getLeftHandExprList().getExpressionList(), |
| 185 | + expression -> isResolvedTo(expression, varDefinition)); |
| 186 | + if (structReferenceExpression == null) return null; |
| 187 | + GoExpression compositeLit = GoPsiImplUtil.getRightExpression(structAssignment, structReferenceExpression); |
| 188 | + return ObjectUtils.tryCast(compositeLit, GoCompositeLit.class); |
| 189 | + } |
| 190 | + |
| 191 | + private static boolean hasStructTypeWithField(@NotNull GoVarDefinition structVarDefinition, @NotNull GoNamedElement field) { |
| 192 | + GoType type = structVarDefinition.getGoType(null); |
| 193 | + GoStructType structType = type != null ? ObjectUtils.tryCast(type.getUnderlyingType(), GoStructType.class) : null; |
| 194 | + return structType != null && PsiTreeUtil.isAncestor(structType, field, true); |
| 195 | + } |
| 196 | + |
| 197 | + private static boolean isFieldInitialization(@NotNull GoElement element, @NotNull PsiElement field) { |
| 198 | + GoKey key = element.getKey(); |
| 199 | + GoFieldName fieldName = key != null ? key.getFieldName() : null; |
| 200 | + return fieldName != null && fieldName.resolve() == field; |
| 201 | + } |
| 202 | + |
| 203 | + @NotNull |
| 204 | + private static List<GoReferenceExpression> getUninitializedFieldReferenceExpressions(@NotNull GoAssignmentStatement assignment, |
| 205 | + @NotNull GoCompositeLit structLiteral) { |
| 206 | + return filter(getFieldReferenceExpressions(assignment), expression -> |
| 207 | + isUninitializedFieldReferenceExpression(expression, structLiteral) && !isAssignedInPreviousStatement(expression, assignment)); |
| 208 | + } |
| 209 | + |
| 210 | + @Contract("null, _-> false") |
| 211 | + private static boolean isUninitializedFieldReferenceExpression(@Nullable GoReferenceExpression fieldReferenceExpression, |
| 212 | + @NotNull GoCompositeLit structLiteral) { |
| 213 | + if (fieldReferenceExpression == null) return false; |
| 214 | + GoLiteralValue literalValue = structLiteral.getLiteralValue(); |
| 215 | + PsiElement resolve = fieldReferenceExpression.resolve(); |
| 216 | + return literalValue != null && isFieldDefinition(resolve) && |
| 217 | + !exists(literalValue.getElementList(), element -> isFieldInitialization(element, resolve)); |
| 218 | + } |
| 219 | + |
| 220 | + @NotNull |
| 221 | + private static List<? extends PsiElement> getLeftHandElements(@NotNull GoStatement statement) { |
| 222 | + if (statement instanceof GoSimpleStatement) { |
| 223 | + GoShortVarDeclaration varDeclaration = ((GoSimpleStatement)statement).getShortVarDeclaration(); |
| 224 | + return varDeclaration != null ? varDeclaration.getVarDefinitionList() : emptyList(); |
| 225 | + } |
| 226 | + if (statement instanceof GoAssignmentStatement) { |
| 227 | + return ((GoAssignmentStatement)statement).getLeftHandExprList().getExpressionList(); |
| 228 | + } |
| 229 | + return emptyList(); |
| 230 | + } |
| 231 | + |
| 232 | + @Override |
| 233 | + public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { |
| 234 | + Data data = getData(element); |
| 235 | + if (data == null) return; |
| 236 | + moveFieldReferenceExpressions(data); |
| 237 | + } |
| 238 | + |
| 239 | + private static void moveFieldReferenceExpressions(@NotNull Data data) { |
| 240 | + GoLiteralValue literalValue = data.getCompositeLit().getLiteralValue(); |
| 241 | + if (literalValue == null) return; |
| 242 | + |
| 243 | + for (GoReferenceExpression expression : data.getReferenceExpressions()) { |
| 244 | + GoExpression parentExpression = PsiTreeUtil.getTopmostParentOfType(expression, GoExpression.class); |
| 245 | + GoExpression anchor = parentExpression != null ? parentExpression : expression; |
| 246 | + GoExpression fieldValue = GoPsiImplUtil.getRightExpression(data.getAssignment(), anchor); |
| 247 | + if (fieldValue == null) continue; |
| 248 | + |
| 249 | + GoPsiImplUtil.deleteExpressionFromAssignment(data.getAssignment(), anchor); |
| 250 | + addFieldDefinition(literalValue, expression.getIdentifier().getText(), fieldValue.getText()); |
| 251 | + } |
| 252 | + } |
| 253 | + |
| 254 | + private static void addFieldDefinition(@NotNull GoLiteralValue literalValue, @NotNull String name, @NotNull String value) { |
| 255 | + Project project = literalValue.getProject(); |
| 256 | + PsiElement newField = GoElementFactory.createLiteralValueElement(project, name, value); |
| 257 | + GoElement lastElement = getLastItem(literalValue.getElementList()); |
| 258 | + if (lastElement == null) { |
| 259 | + literalValue.addAfter(newField, literalValue.getLbrace()); |
| 260 | + } |
| 261 | + else { |
| 262 | + lastElement.add(GoElementFactory.createComma(project)); |
| 263 | + lastElement.add(newField); |
| 264 | + } |
| 265 | + } |
| 266 | + |
| 267 | + private static class Data { |
| 268 | + private final GoCompositeLit myCompositeLit; |
| 269 | + private final GoAssignmentStatement myAssignment; |
| 270 | + private final List<GoReferenceExpression> myReferenceExpressions; |
| 271 | + |
| 272 | + public Data(@NotNull GoAssignmentStatement assignment, |
| 273 | + @NotNull GoCompositeLit compositeLit, |
| 274 | + @NotNull List<GoReferenceExpression> referenceExpressions) { |
| 275 | + myCompositeLit = compositeLit; |
| 276 | + myAssignment = assignment; |
| 277 | + myReferenceExpressions = referenceExpressions; |
| 278 | + } |
| 279 | + |
| 280 | + public GoCompositeLit getCompositeLit() { |
| 281 | + return myCompositeLit; |
| 282 | + } |
| 283 | + |
| 284 | + public GoAssignmentStatement getAssignment() { |
| 285 | + return myAssignment; |
| 286 | + } |
| 287 | + |
| 288 | + public List<GoReferenceExpression> getReferenceExpressions() { |
| 289 | + return myReferenceExpressions; |
| 290 | + } |
| 291 | + } |
| 292 | +} |
0 commit comments