2222import com .goide .util .GoUtil ;
2323import com .intellij .codeInspection .*;
2424import com .intellij .codeInspection .ui .SingleCheckboxOptionsPanel ;
25- import com .intellij .openapi .progress .ProgressManager ;
2625import com .intellij .openapi .project .Project ;
26+ import com .intellij .openapi .util .Comparing ;
2727import com .intellij .openapi .util .InvalidDataException ;
2828import com .intellij .openapi .util .WriteExternalException ;
2929import com .intellij .psi .PsiElement ;
3030import com .intellij .psi .util .PsiTreeUtil ;
31- import com .intellij .util .containers . ContainerUtil ;
31+ import com .intellij .util .ObjectUtils ;
3232import org .jdom .Element ;
33+ import org .jetbrains .annotations .Contract ;
3334import org .jetbrains .annotations .NotNull ;
3435import org .jetbrains .annotations .Nullable ;
3536
3637import javax .swing .*;
3738import java .util .List ;
3839
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+
3945public 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 ();
4148 public boolean reportLocalStructs ;
4249 /**
43- * @deprecated use reportLocalStructs
50+ * @deprecated use {@link # reportLocalStructs}
4451 */
4552 @ SuppressWarnings ("WeakerAccess" ) public Boolean reportImportedStructs ;
4653
@@ -49,67 +56,114 @@ public class GoStructInitializationInspection extends GoInspectionBase {
4956 protected GoVisitor buildGoVisitor (@ NotNull ProblemsHolder holder , @ NotNull LocalInspectionToolSession session ) {
5057 return new GoVisitor () {
5158 @ 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 );
6074 }
6175 }
6276 };
6377 }
6478
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 ;
6983
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 ;
7487 }
7588
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 ;
9096 }
9197
9298 @ 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" );
97139 }
98140
99141 private static class GoReplaceWithNamedStructFieldQuickFix extends LocalQuickFixBase {
100- private String myStructField ;
101142
102- public GoReplaceWithNamedStructFieldQuickFix (@ NotNull String structField ) {
143+ public GoReplaceWithNamedStructFieldQuickFix () {
103144 super (REPLACE_WITH_NAMED_STRUCT_FIELD_FIX_NAME );
104- myStructField = structField ;
105145 }
106146
107147 @ Override
108148 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 ()));
113167 }
114168 }
115169
0 commit comments