22
22
import com .goide .util .GoUtil ;
23
23
import com .intellij .codeInspection .*;
24
24
import com .intellij .codeInspection .ui .SingleCheckboxOptionsPanel ;
25
- import com .intellij .openapi .progress .ProgressManager ;
26
25
import com .intellij .openapi .project .Project ;
26
+ import com .intellij .openapi .util .Comparing ;
27
27
import com .intellij .openapi .util .InvalidDataException ;
28
28
import com .intellij .openapi .util .WriteExternalException ;
29
29
import com .intellij .psi .PsiElement ;
30
30
import com .intellij .psi .util .PsiTreeUtil ;
31
- import com .intellij .util .containers . ContainerUtil ;
31
+ import com .intellij .util .ObjectUtils ;
32
32
import org .jdom .Element ;
33
+ import org .jetbrains .annotations .Contract ;
33
34
import org .jetbrains .annotations .NotNull ;
34
35
import org .jetbrains .annotations .Nullable ;
35
36
36
37
import javax .swing .*;
37
38
import java .util .List ;
38
39
40
+ import static com .intellij .util .containers .ContainerUtil .*;
41
+ import static java .lang .Math .min ;
42
+ import static java .util .stream .Collectors .toList ;
43
+ import static java .util .stream .IntStream .range ;
44
+
39
45
public class GoStructInitializationInspection extends GoInspectionBase {
40
- public static final String REPLACE_WITH_NAMED_STRUCT_FIELD_FIX_NAME = "Replace with named struct field" ;
46
+ public static final String REPLACE_WITH_NAMED_STRUCT_FIELD_FIX_NAME = "Replace with named struct fields" ;
47
+ private static final GoReplaceWithNamedStructFieldQuickFix QUICK_FIX = new GoReplaceWithNamedStructFieldQuickFix ();
41
48
public boolean reportLocalStructs ;
42
49
/**
43
- * @deprecated use reportLocalStructs
50
+ * @deprecated use {@link # reportLocalStructs}
44
51
*/
45
52
@ SuppressWarnings ("WeakerAccess" ) public Boolean reportImportedStructs ;
46
53
@@ -49,67 +56,114 @@ public class GoStructInitializationInspection extends GoInspectionBase {
49
56
protected GoVisitor buildGoVisitor (@ NotNull ProblemsHolder holder , @ NotNull LocalInspectionToolSession session ) {
50
57
return new GoVisitor () {
51
58
@ Override
52
- public void visitLiteralValue (@ NotNull GoLiteralValue o ) {
53
- if (PsiTreeUtil .getParentOfType (o , GoReturnStatement .class , GoShortVarDeclaration .class , GoAssignmentStatement .class ) == null ) {
54
- return ;
55
- }
56
- PsiElement parent = o .getParent ();
57
- GoType refType = GoPsiImplUtil .getLiteralType (parent , false );
58
- if (refType instanceof GoStructType ) {
59
- processStructType (holder , o , (GoStructType )refType );
59
+ public void visitLiteralValue (@ NotNull GoLiteralValue literalValue ) {
60
+ GoStructType structType = getLiteralStructType (literalValue );
61
+ if (structType == null || !isStructImportedOrLocalAllowed (structType , literalValue )) return ;
62
+
63
+ List <GoElement > elements = literalValue .getElementList ();
64
+ List <GoNamedElement > definitions = getFieldDefinitions (structType );
65
+
66
+ if (!areElementsKeysMatchesDefinitions (elements , definitions )) return ;
67
+ registerProblemsForElementsWithoutKeys (elements , definitions .size ());
68
+ }
69
+
70
+ private void registerProblemsForElementsWithoutKeys (@ NotNull List <GoElement > elements , int definitionsCount ) {
71
+ for (int i = 0 ; i < min (elements .size (), definitionsCount ); i ++) {
72
+ if (elements .get (i ).getKey () != null ) continue ;
73
+ holder .registerProblem (elements .get (i ), "Unnamed field initialization" , ProblemHighlightType .WEAK_WARNING , QUICK_FIX );
60
74
}
61
75
}
62
76
};
63
77
}
64
78
65
- @ Override
66
- public JComponent createOptionsPanel ( ) {
67
- return new SingleCheckboxOptionsPanel ( "Report for local type definitions as well" , this , "reportLocalStructs" );
68
- }
79
+ @ Contract ( "null -> null" )
80
+ private static GoStructType getLiteralStructType ( @ Nullable GoLiteralValue literalValue ) {
81
+ GoCompositeLit parentLit = GoPsiTreeUtil . getDirectParentOfType ( literalValue , GoCompositeLit . class );
82
+ if ( parentLit != null && ! isStructLit ( parentLit )) return null ;
69
83
70
- private void processStructType (@ NotNull ProblemsHolder holder , @ NotNull GoLiteralValue element , @ NotNull GoStructType structType ) {
71
- if (reportLocalStructs || !GoUtil .inSamePackage (structType .getContainingFile (), element .getContainingFile ())) {
72
- processLiteralValue (holder , element , structType .getFieldDeclarationList ());
73
- }
84
+ GoStructType litType = ObjectUtils .tryCast (GoPsiImplUtil .getLiteralType (literalValue , parentLit == null ), GoStructType .class );
85
+ GoNamedElement definition = getFieldDefinition (GoPsiTreeUtil .getDirectParentOfType (literalValue , GoValue .class ));
86
+ return definition != null && litType != null ? getUnderlyingStructType (definition .getGoType (null )) : litType ;
74
87
}
75
88
76
- private static void processLiteralValue (@ NotNull ProblemsHolder holder ,
77
- @ NotNull GoLiteralValue o ,
78
- @ NotNull List <GoFieldDeclaration > fields ) {
79
- List <GoElement > vals = o .getElementList ();
80
- for (int elemId = 0 ; elemId < vals .size (); elemId ++) {
81
- ProgressManager .checkCanceled ();
82
- GoElement element = vals .get (elemId );
83
- if (element .getKey () == null && elemId < fields .size ()) {
84
- String structFieldName = getFieldName (fields .get (elemId ));
85
- LocalQuickFix [] fixes = structFieldName != null ? new LocalQuickFix []{new GoReplaceWithNamedStructFieldQuickFix (structFieldName )}
86
- : LocalQuickFix .EMPTY_ARRAY ;
87
- holder .registerProblem (element , "Unnamed field initialization" , ProblemHighlightType .GENERIC_ERROR_OR_WARNING , fixes );
88
- }
89
- }
89
+ @ Nullable
90
+ private static GoNamedElement getFieldDefinition (@ Nullable GoValue value ) {
91
+ GoKey key = PsiTreeUtil .getPrevSiblingOfType (value , GoKey .class );
92
+ GoFieldName fieldName = key != null ? key .getFieldName () : null ;
93
+ PsiElement field = fieldName != null ? fieldName .resolve () : null ;
94
+ return field instanceof GoAnonymousFieldDefinition || field instanceof GoFieldDefinition ? ObjectUtils
95
+ .tryCast (field , GoNamedElement .class ) : null ;
90
96
}
91
97
92
98
@ Nullable
93
- private static String getFieldName (@ NotNull GoFieldDeclaration declaration ) {
94
- List <GoFieldDefinition > list = declaration .getFieldDefinitionList ();
95
- GoFieldDefinition fieldDefinition = ContainerUtil .getFirstItem (list );
96
- return fieldDefinition != null ? fieldDefinition .getIdentifier ().getText () : null ;
99
+ @ Contract ("null -> null" )
100
+ private static GoStructType getUnderlyingStructType (@ Nullable GoType type ) {
101
+ return type != null ? ObjectUtils .tryCast (type .getUnderlyingType (), GoStructType .class ) : null ;
102
+ }
103
+
104
+ private static boolean isStructLit (@ NotNull GoCompositeLit compositeLit ) {
105
+ return getUnderlyingStructType (compositeLit .getGoType (null )) != null ;
106
+ }
107
+
108
+ private boolean isStructImportedOrLocalAllowed (@ NotNull GoStructType structType , @ NotNull GoLiteralValue literalValue ) {
109
+ return reportLocalStructs || !GoUtil .inSamePackage (structType .getContainingFile (), literalValue .getContainingFile ());
110
+ }
111
+
112
+ private static boolean areElementsKeysMatchesDefinitions (@ NotNull List <GoElement > elements , @ NotNull List <GoNamedElement > definitions ) {
113
+ return range (0 , elements .size ()).allMatch (i -> isNullOrNamesEqual (elements .get (i ).getKey (), GoPsiImplUtil .getByIndex (definitions , i )));
114
+ }
115
+
116
+ @ Contract ("null, _ -> true; !null, null -> false" )
117
+ private static boolean isNullOrNamesEqual (@ Nullable GoKey key , @ Nullable GoNamedElement elementToCompare ) {
118
+ return key == null || elementToCompare != null && Comparing .equal (key .getText (), elementToCompare .getName ());
119
+ }
120
+
121
+ @ NotNull
122
+ private static List <GoNamedElement > getFieldDefinitions (@ Nullable GoStructType type ) {
123
+ return type != null ? type .getFieldDeclarationList ().stream ()
124
+ .flatMap (declaration -> getFieldDefinitions (declaration ).stream ())
125
+ .collect (toList ()) : emptyList ();
126
+ }
127
+
128
+ @ NotNull
129
+ private static List <GoNamedElement > getFieldDefinitions (@ NotNull GoFieldDeclaration declaration ) {
130
+ GoNamedElement anonymousDefinition = ObjectUtils .tryCast (declaration .getAnonymousFieldDefinition (), GoNamedElement .class );
131
+ return anonymousDefinition != null
132
+ ? list (anonymousDefinition )
133
+ : map (declaration .getFieldDefinitionList (), definition -> ObjectUtils .tryCast (definition , GoNamedElement .class ));
134
+ }
135
+
136
+ @ Override
137
+ public JComponent createOptionsPanel () {
138
+ return new SingleCheckboxOptionsPanel ("Report for local type definitions as well" , this , "reportLocalStructs" );
97
139
}
98
140
99
141
private static class GoReplaceWithNamedStructFieldQuickFix extends LocalQuickFixBase {
100
- private String myStructField ;
101
142
102
- public GoReplaceWithNamedStructFieldQuickFix (@ NotNull String structField ) {
143
+ public GoReplaceWithNamedStructFieldQuickFix () {
103
144
super (REPLACE_WITH_NAMED_STRUCT_FIELD_FIX_NAME );
104
- myStructField = structField ;
105
145
}
106
146
107
147
@ Override
108
148
public void applyFix (@ NotNull Project project , @ NotNull ProblemDescriptor descriptor ) {
109
- PsiElement startElement = descriptor .getStartElement ();
110
- if (startElement instanceof GoElement ) {
111
- startElement .replace (GoElementFactory .createLiteralValueElement (project , myStructField , startElement .getText ()));
112
- }
149
+ PsiElement element = ObjectUtils .tryCast (descriptor .getStartElement (), GoElement .class );
150
+ GoLiteralValue literal = element != null && element .isValid () ? PsiTreeUtil .getParentOfType (element , GoLiteralValue .class ) : null ;
151
+
152
+ List <GoElement > elements = literal != null ? literal .getElementList () : emptyList ();
153
+ List <GoNamedElement > definitions = getFieldDefinitions (getLiteralStructType (literal ));
154
+ if (!areElementsKeysMatchesDefinitions (elements , definitions )) return ;
155
+ addKeysToElements (project , elements , definitions );
156
+ }
157
+ }
158
+
159
+ private static void addKeysToElements (@ NotNull Project project ,
160
+ @ NotNull List <GoElement > elements ,
161
+ @ NotNull List <GoNamedElement > definitions ) {
162
+ for (int i = 0 ; i < min (elements .size (), definitions .size ()); i ++) {
163
+ GoElement element = elements .get (i );
164
+ String fieldDefinitionName = definitions .get (i ).getName ();
165
+ GoValue value = fieldDefinitionName != null && element .getKey () == null ? element .getValue () : null ;
166
+ if (value != null ) element .replace (GoElementFactory .createLiteralValueElement (project , fieldDefinitionName , value .getText ()));
113
167
}
114
168
}
115
169
0 commit comments