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,115 @@ 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+
117+ @ Contract ("null, _ -> true" )
118+ private static boolean isNullOrNamesEqual (@ Nullable GoKey key , @ Nullable GoNamedElement elementToCompare ) {
119+ return key == null || elementToCompare != null && Comparing .equal (key .getText (), elementToCompare .getName ());
120+ }
121+
122+ @ NotNull
123+ private static List <GoNamedElement > getFieldDefinitions (@ Nullable GoStructType type ) {
124+ return type != null ? type .getFieldDeclarationList ().stream ()
125+ .flatMap (declaration -> getFieldDefinitions (declaration ).stream ())
126+ .collect (toList ()) : emptyList ();
127+ }
128+
129+ @ NotNull
130+ private static List <GoNamedElement > getFieldDefinitions (@ NotNull GoFieldDeclaration declaration ) {
131+ GoNamedElement anonymousDefinition = ObjectUtils .tryCast (declaration .getAnonymousFieldDefinition (), GoNamedElement .class );
132+ return anonymousDefinition != null
133+ ? list (anonymousDefinition )
134+ : map (declaration .getFieldDefinitionList (), definition -> ObjectUtils .tryCast (definition , GoNamedElement .class ));
135+ }
136+
137+ @ Override
138+ public JComponent createOptionsPanel () {
139+ return new SingleCheckboxOptionsPanel ("Report for local type definitions as well" , this , "reportLocalStructs" );
97140 }
98141
99142 private static class GoReplaceWithNamedStructFieldQuickFix extends LocalQuickFixBase {
100- private String myStructField ;
101143
102- public GoReplaceWithNamedStructFieldQuickFix (@ NotNull String structField ) {
144+ public GoReplaceWithNamedStructFieldQuickFix () {
103145 super (REPLACE_WITH_NAMED_STRUCT_FIELD_FIX_NAME );
104- myStructField = structField ;
105146 }
106147
107148 @ Override
108149 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- }
150+ PsiElement element = ObjectUtils .tryCast (descriptor .getStartElement (), GoElement .class );
151+ GoLiteralValue literal = element != null && element .isValid () ? PsiTreeUtil .getParentOfType (element , GoLiteralValue .class ) : null ;
152+
153+ List <GoElement > elements = literal != null ? literal .getElementList () : emptyList ();
154+ List <GoNamedElement > fieldDefinitionNames = getFieldDefinitions (getLiteralStructType (literal ));
155+ if (!areElementsKeysMatchesDefinitions (elements , fieldDefinitionNames )) return ;
156+ addKeysToElements (project , elements , fieldDefinitionNames );
157+ }
158+ }
159+
160+ private static void addKeysToElements (@ NotNull Project project ,
161+ @ NotNull List <GoElement > elements ,
162+ @ NotNull List <GoNamedElement > fieldDefinitions ) {
163+ for (int i = 0 ; i < min (elements .size (), fieldDefinitions .size ()); i ++) {
164+ GoElement element = elements .get (i );
165+ String fieldDefinitionName = fieldDefinitions .get (i ).getName ();
166+ GoValue value = fieldDefinitionName != null && element .getKey () == null ? element .getValue () : null ;
167+ if (value != null ) element .replace (GoElementFactory .createLiteralValueElement (project , fieldDefinitionName , value .getText ()));
113168 }
114169 }
115170
0 commit comments