Skip to content

Commit 0bfabe5

Browse files
grenkiignatov
grenki
authored andcommitted
Move to struct assignment intention (#2806)
fixes #2702
1 parent 0632a7c commit 0bfabe5

File tree

57 files changed

+1033
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1033
-3
lines changed

resources/META-INF/gogland.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@
181181
<categoryKey>go.intentions.category</categoryKey>
182182
<className>com.goide.intentions.GoAddFunctionBlockIntention</className>
183183
</intentionAction>
184+
<intentionAction>
185+
<bundleName>com.goide.GoBundle</bundleName>
186+
<categoryKey>go.intentions.category</categoryKey>
187+
<className>com.goide.intentions.GoMoveToStructInitializationIntention</className>
188+
</intentionAction>
184189
<!-- unused inspections -->
185190
<localInspection language="go" displayName="Unused import inspection" groupPath="Go"
186191
groupName="Declaration redundancy" enabledByDefault="true" level="ERROR"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
s := S{<spot>foo: `bar`</spot>}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
s := S{}
2+
<spot>s.foo = `bar`</spot>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
<html>
18+
<body>
19+
This intention moves struct field assignment to struct initialization block.
20+
</body>
21+
</html>

src/com/goide/inspections/GoStructInitializationInspection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public GoReplaceWithNamedStructFieldQuickFix(@NotNull String structField) {
108108
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
109109
PsiElement startElement = descriptor.getStartElement();
110110
if (startElement instanceof GoElement) {
111-
startElement.replace(GoElementFactory.createNamedStructField(project, myStructField, startElement.getText()));
111+
startElement.replace(GoElementFactory.createLiteralValueElement(project, myStructField, startElement.getText()));
112112
}
113113
}
114114
}
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
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+
}

src/com/goide/psi/impl/GoElementFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,8 @@ public static GoType createType(@NotNull Project project, @NotNull String text)
256256
return PsiTreeUtil.findChildOfType(file, GoType.class);
257257
}
258258

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

src/com/goide/psi/impl/GoPsiImplUtil.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,20 @@ public static void deleteSpec(@NotNull GoConstDeclaration declaration, @NotNull
13541354
specToDelete.delete();
13551355
}
13561356

1357+
public static void deleteExpressionFromAssignment(@NotNull GoAssignmentStatement assignment,
1358+
@NotNull GoExpression expressionToDelete) {
1359+
GoExpression expressionValue = getRightExpression(assignment, expressionToDelete);
1360+
if (expressionValue != null) {
1361+
if (assignment.getExpressionList().size() == 1) {
1362+
assignment.delete();
1363+
}
1364+
else {
1365+
deleteElementFromCommaSeparatedList(expressionToDelete);
1366+
deleteElementFromCommaSeparatedList(expressionValue);
1367+
}
1368+
}
1369+
}
1370+
13571371
public static void deleteDefinition(@NotNull GoVarSpec spec, @NotNull GoVarDefinition definitionToDelete) {
13581372
List<GoVarDefinition> definitionList = spec.getVarDefinitionList();
13591373
int index = definitionList.indexOf(definitionToDelete);
@@ -1679,4 +1693,10 @@ private static PsiElement getNotNullElement(@Nullable PsiElement... elements) {
16791693
public static boolean isSingleCharLiteral(@NotNull GoStringLiteral literal) {
16801694
return literal.getDecodedText().length() == 1;
16811695
}
1696+
1697+
@Nullable
1698+
public static GoExpression getRightExpression(@NotNull GoAssignmentStatement assignment, @NotNull GoExpression leftExpression) {
1699+
int fieldIndex = assignment.getLeftHandExprList().getExpressionList().indexOf(leftExpression);
1700+
return getByIndex(assignment.getExpressionList(), fieldIndex);
1701+
}
16821702
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package main
2+
3+
type S struct {
4+
string
5+
}
6+
7+
func main() {
8+
s := S{string: "bar"}
9+
_ = "foo"
10+
print(s.string)
11+
}

0 commit comments

Comments
 (0)