Skip to content

Commit adbba13

Browse files
committed
Rework GoStructInitializationInspection
fixes #2819
1 parent eb669fc commit adbba13

27 files changed

+441
-233
lines changed

Diff for: resources/META-INF/gogland.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@
256256
implementationClass="com.goide.inspections.GoExportedOwnDeclarationInspection"/>
257257
<localInspection language="go" displayName="Struct initialization without field names" groupPath="Go"
258258
groupName="Code style issues" enabledByDefault="true" level="WEAK WARNING"
259-
implementationClass="com.goide.inspections.GoStructInitializationInspection"/>
259+
implementationClass="com.goide.inspections.GoStructInitializationWithUnnamedFieldInspection"/>
260260
<localInspection language="go" displayName="Receiver has generic name" groupPath="Go"
261261
groupName="Code style issues" enabledByDefault="true" level="WEAK WARNING"
262262
implementationClass="com.goide.inspections.GoReceiverNamesInspection"/>

Diff for: src/com/goide/inspections/GoStructInitializationInspection.java

-129
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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.inspections;
18+
19+
import com.goide.psi.*;
20+
import com.goide.psi.impl.GoElementFactory;
21+
import com.goide.psi.impl.GoPsiImplUtil;
22+
import com.goide.util.GoUtil;
23+
import com.intellij.codeInspection.*;
24+
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
25+
import com.intellij.openapi.project.Project;
26+
import com.intellij.openapi.util.InvalidDataException;
27+
import com.intellij.openapi.util.WriteExternalException;
28+
import com.intellij.util.ObjectUtils;
29+
import org.jdom.Element;
30+
import org.jetbrains.annotations.Contract;
31+
import org.jetbrains.annotations.NotNull;
32+
import org.jetbrains.annotations.Nullable;
33+
34+
import javax.swing.*;
35+
import java.util.List;
36+
37+
import static com.intellij.openapi.util.Comparing.equal;
38+
import static com.intellij.util.containers.ContainerUtil.*;
39+
import static java.util.stream.Collectors.toList;
40+
import static java.util.stream.IntStream.range;
41+
42+
public class GoStructInitializationWithUnnamedFieldInspection extends GoInspectionBase {
43+
public static final String REPLACE_WITH_NAMED_STRUCT_FIELD_FIX_NAME = "Replace with named struct fields";
44+
private static final GoReplaceWithNamedStructFieldQuickFix QUICK_FIX = new GoReplaceWithNamedStructFieldQuickFix();
45+
public boolean reportLocalStructs;
46+
/**
47+
* @deprecated use {@link #reportLocalStructs}
48+
*/
49+
@SuppressWarnings("WeakerAccess") public Boolean reportImportedStructs;
50+
51+
@NotNull
52+
@Override
53+
protected GoVisitor buildGoVisitor(@NotNull ProblemsHolder holder, @NotNull LocalInspectionToolSession session) {
54+
return new GoVisitor() {
55+
56+
@Override
57+
public void visitCompositeLit(@NotNull GoCompositeLit compositeLit) {
58+
GoLiteralValue literalValue = compositeLit.getLiteralValue();
59+
GoStructType structType = getStructType(literalValue);
60+
if (structType == null || !isStructPackageLocationValid(literalValue, structType)) return;
61+
62+
List<GoElement> elements = literalValue.getElementList();
63+
if (hasUnnamedElement(elements) && areElementsNamesMatchesDefinitions(getNames(elements), getFieldDefinitionsNames(structType))) {
64+
holder.registerProblem(literalValue, "Unnamed field initializations", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, QUICK_FIX);
65+
}
66+
}
67+
};
68+
}
69+
70+
@Nullable
71+
@Contract("null -> null")
72+
private static GoStructType getStructType(@Nullable GoLiteralValue literal) {
73+
return literal != null ? ObjectUtils.tryCast(GoPsiImplUtil.getLiteralType(literal.getParent(), false), GoStructType.class) : null;
74+
}
75+
76+
private boolean isStructPackageLocationValid(GoLiteralValue literalValue, GoStructType structType) {
77+
return reportLocalStructs || !GoUtil.inSamePackage(literalValue.getContainingFile(), structType.getContainingFile());
78+
}
79+
80+
private static boolean hasUnnamedElement(@NotNull List<GoElement> elements) {
81+
return exists(elements, element -> element.getKey() == null);
82+
}
83+
84+
@NotNull
85+
private static List<String> getNames(@NotNull List<GoElement> elements) {
86+
return map(elements, element -> {
87+
GoKey key = element.getKey();
88+
return key != null ? key.getText() : null;
89+
});
90+
}
91+
92+
private static boolean areElementsNamesMatchesDefinitions(@NotNull List<String> elementsNames,
93+
@NotNull List<String> fieldDefinitionsNames) {
94+
return range(0, elementsNames.size())
95+
.allMatch(index -> elementsNames.get(index) == null || equal(elementsNames.get(index), getByIndex(fieldDefinitionsNames, index)));
96+
}
97+
98+
@Nullable
99+
private static String getByIndex(@NotNull List<String> list, int index) {
100+
return 0 <= index && index < list.size() ? list.get(index) : null;
101+
}
102+
103+
@NotNull
104+
private static List<String> getFieldDefinitionsNames(@NotNull GoStructType type) {
105+
return type.getFieldDeclarationList().stream()
106+
.flatMap(declaration -> getFieldDefinitionsNames(declaration).stream())
107+
.collect(toList());
108+
}
109+
110+
@NotNull
111+
private static List<String> getFieldDefinitionsNames(@NotNull GoFieldDeclaration declaration) {
112+
GoAnonymousFieldDefinition definition = declaration.getAnonymousFieldDefinition();
113+
return definition != null ? list(definition.getName()) : map(declaration.getFieldDefinitionList(), GoNamedElement::getName);
114+
}
115+
116+
@Override
117+
public JComponent createOptionsPanel() {
118+
return new SingleCheckboxOptionsPanel("Report for local type definitions as well", this, "reportLocalStructs");
119+
}
120+
121+
private static class GoReplaceWithNamedStructFieldQuickFix extends LocalQuickFixBase {
122+
123+
public GoReplaceWithNamedStructFieldQuickFix() {
124+
super(REPLACE_WITH_NAMED_STRUCT_FIELD_FIX_NAME);
125+
}
126+
127+
@Override
128+
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
129+
GoLiteralValue literal = ObjectUtils.tryCast(descriptor.getStartElement(), GoLiteralValue.class);
130+
GoStructType structType = getStructType(literal);
131+
List<GoElement> elements = structType != null ? literal.getElementList() : emptyList();
132+
List<String> fieldDefinitionNames = structType != null ? getFieldDefinitionsNames(structType) : emptyList();
133+
if (!areElementsNamesMatchesDefinitions(getNames(elements), fieldDefinitionNames)) return;
134+
replaceElementsByNamed(elements, fieldDefinitionNames, project);
135+
}
136+
}
137+
138+
private static void replaceElementsByNamed(@NotNull List<GoElement> elements,
139+
@NotNull List<String> fieldDefinitionNames,
140+
@NotNull Project project) {
141+
for (int i = 0; i < elements.size(); i++) {
142+
GoElement element = elements.get(i);
143+
String fieldDefinitionName = getByIndex(fieldDefinitionNames, i);
144+
GoValue value = fieldDefinitionName != null && element.getKey() == null ? element.getValue() : null;
145+
if (value == null) continue;
146+
147+
GoElement namedElement = GoElementFactory.createLiteralValueElement(project, fieldDefinitionName, value.getText());
148+
element.replace(namedElement);
149+
}
150+
}
151+
152+
@Override
153+
public void readSettings(@NotNull Element node) throws InvalidDataException {
154+
super.readSettings(node);
155+
if (reportImportedStructs != null) {
156+
reportLocalStructs = reportImportedStructs;
157+
}
158+
}
159+
160+
@Override
161+
public void writeSettings(@NotNull Element node) throws WriteExternalException {
162+
reportImportedStructs = null;
163+
super.writeSettings(node);
164+
}
165+
}

Diff for: src/com/goide/psi/impl/GoElementFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ public static GoType createType(@NotNull Project project, @NotNull String text)
256256
return PsiTreeUtil.findChildOfType(file, GoType.class);
257257
}
258258

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

Diff for: testData/inspections/go-struct-initialization/quickFix.go

-9
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package foo
2+
3+
type S struct {
4+
X string
5+
string
6+
Y int
7+
}
8+
func main() {
9+
var s S
10+
s = S{X: "X", string: "a", Y: 1}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package foo
2+
3+
type S struct {
4+
X string
5+
string
6+
Y int
7+
}
8+
func main() {
9+
var s S
10+
s = S<weak_warning descr="Unnamed field initializations">{<caret>"X", "a", Y: 1}</weak_warning>
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package foo
2+
3+
type S struct {
4+
X, Y int
5+
}
6+
func main() {
7+
s := S{X: 1, Y: 0, 2}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package foo
2+
3+
type S struct {
4+
X, Y int
5+
}
6+
func main() {
7+
s := S<weak_warning descr="Unnamed field initializations">{<caret>1, 0, 2}</weak_warning>
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package foo
2+
3+
type S struct {
4+
X, Y int
5+
}
6+
func main() {
7+
s := S{<caret>1, 0, X: 2}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package foo
2+
3+
func main() {
4+
type B struct {
5+
Y int
6+
}
7+
8+
type S struct {
9+
X int
10+
B
11+
Z int
12+
}
13+
14+
s := S{X: 1, B: B{Y: 2}, Z: 3}
15+
print(s.B.Y)
16+
}

0 commit comments

Comments
 (0)