diff --git a/src/com/goide/inspections/unresolved/GoAddStructFieldFix.java b/src/com/goide/inspections/unresolved/GoAddStructFieldFix.java new file mode 100644 index 0000000000..04ad5f6152 --- /dev/null +++ b/src/com/goide/inspections/unresolved/GoAddStructFieldFix.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013-2017 Sergey Ignatov, Alexander Zolotov, Florin Patan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.goide.inspections.unresolved; + +import com.goide.GoConstants; +import com.goide.psi.*; +import com.goide.psi.impl.GoPsiImplUtil; +import com.goide.util.GoUtil; +import com.intellij.codeInsight.template.Template; +import com.intellij.codeInsight.template.TemplateManager; +import com.intellij.codeInsight.template.impl.ConstantNode; +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class GoAddStructFieldFix extends LocalQuickFixAndIntentionActionOnPsiElement { + public static final String QUICK_FIX_NAME = "Add missing field"; + + protected GoAddStructFieldFix(@NotNull PsiElement element) { + super(element); + } + + @NotNull + @Override + public String getText() { + return QUICK_FIX_NAME; + } + + @Override + public void invoke(@NotNull Project project, + @NotNull PsiFile file, + @Nullable Editor editor, + @NotNull PsiElement startElement, + @NotNull PsiElement endElement) { + if (editor == null) return; + GoReferenceExpression referenceExpression = ObjectUtils.tryCast(startElement, GoReferenceExpression.class); + GoStructType structType = referenceExpression != null ? resolveStructType(referenceExpression) : null; + if (structType == null) return; + + List declarations = structType.getFieldDeclarationList(); + PsiElement anchor = !declarations.isEmpty() ? ContainerUtil.getLastItem(declarations) : structType.getLbrace(); + if (anchor == null) return; + + startTemplate(project, editor, file, referenceExpression, anchor); + } + + private static void startTemplate(@NotNull Project project, + @NotNull Editor editor, + @NotNull PsiFile file, + GoReferenceExpression referenceExpression, + PsiElement anchor) { + Template template = TemplateManager.getInstance(project).createTemplate("", ""); + template.addTextSegment(referenceExpression.getReference().getCanonicalText() + " "); + template.addVariable(new ConstantNode(getTypeName(referenceExpression, file)), true); + template.addTextSegment("\n"); + editor.getCaretModel().moveToOffset(anchor.getTextRange().getEndOffset() + 1); + template.setToReformat(true); + TemplateManager.getInstance(project).startTemplate(editor, template); + } + + + private static String getTypeName(GoReferenceExpression referenceExpression, PsiFile file) { + GoAssignmentStatement assignment = PsiTreeUtil.getParentOfType(referenceExpression, GoAssignmentStatement.class); + if (assignment == null) return GoConstants.INTERFACE_TYPE; + GoExpression expression = GoPsiImplUtil.getRightExpression(assignment, referenceExpression); + GoType type = expression != null ? expression.getGoType(null) : null; + + if (type instanceof GoSpecType) { + GoSpecType spec = (GoSpecType)type; + GoFile typeFile = ObjectUtils.tryCast(spec.getContainingFile(), GoFile.class); + if (typeFile != null && (file.isEquivalentTo(typeFile) || GoUtil.inSamePackage(typeFile, file))) { + return spec.getIdentifier().getText(); + } + } + return type != null ? GoPsiImplUtil.getText(type) : GoConstants.INTERFACE_TYPE; + } + + @Nullable + private static GoStructType resolveStructType(@NotNull GoReferenceExpression referenceExpression) { + GoReferenceExpression qualifier = referenceExpression.getQualifier(); + GoSpecType type = qualifier != null ? ObjectUtils.tryCast(qualifier.getGoType(null), GoSpecType.class) : null; + return type != null ? ObjectUtils.tryCast(type.getType(), GoStructType.class) : null; + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return QUICK_FIX_NAME; + } +} diff --git a/src/com/goide/inspections/unresolved/GoUnresolvedReferenceInspection.java b/src/com/goide/inspections/unresolved/GoUnresolvedReferenceInspection.java index 34444ce28b..1d9733f877 100644 --- a/src/com/goide/inspections/unresolved/GoUnresolvedReferenceInspection.java +++ b/src/com/goide/inspections/unresolved/GoUnresolvedReferenceInspection.java @@ -33,6 +33,7 @@ import com.intellij.psi.formatter.FormatterUtil; import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -74,7 +75,12 @@ public void visitReferenceExpression(@NotNull GoReferenceExpression o) { } else if (reference.resolve() == null) { LocalQuickFix[] fixes = LocalQuickFix.EMPTY_ARRAY; - if (isProhibited(o, qualifier)) { + GoType type = qualifier != null ? qualifier.getGoType(null) : null; + GoStructType structType = type != null ? ObjectUtils.tryCast(type.getUnderlyingType(), GoStructType.class) : null; + if (!"_".equals(reference.getCanonicalText()) && structType != null) { + fixes = new LocalQuickFix[]{new GoAddStructFieldFix(o)}; + } + else if (isProhibited(o, qualifier)) { fixes = createImportPackageFixes(o, reference, holder.isOnTheFly()); } else if (holder.isOnTheFly()) { diff --git a/src/com/goide/psi/impl/GoElementFactory.java b/src/com/goide/psi/impl/GoElementFactory.java index a9c4939ad2..fb523fa240 100644 --- a/src/com/goide/psi/impl/GoElementFactory.java +++ b/src/com/goide/psi/impl/GoElementFactory.java @@ -74,7 +74,7 @@ public static GoIfStatement createIfStatement(@NotNull Project project, public static GoImportDeclaration createEmptyImportDeclaration(@NotNull Project project) { return PsiTreeUtil.findChildOfType(createFileFromText(project, "package main\nimport (\n\n)"), GoImportDeclaration.class); } - + @NotNull public static GoImportDeclaration createImportDeclaration(@NotNull Project project, @NotNull String importString, @Nullable String alias, boolean withParens) { diff --git a/testData/quickfixes/add-struct-field/blank.go b/testData/quickfixes/add-struct-field/blank.go new file mode 100644 index 0000000000..986af3874d --- /dev/null +++ b/testData/quickfixes/add-struct-field/blank.go @@ -0,0 +1,11 @@ +package main + +type S struct { + bb interface{} + cc interface{} +} + +func main() { + s := S{} + s._ = "aa" +} \ No newline at end of file diff --git a/testData/quickfixes/add-struct-field/complexType-after.go b/testData/quickfixes/add-struct-field/complexType-after.go new file mode 100644 index 0000000000..e40003bb7e --- /dev/null +++ b/testData/quickfixes/add-struct-field/complexType-after.go @@ -0,0 +1,12 @@ +package main + +type S struct { + bb interface{} + cc interface{} + aa S +} + +func main() { + s := S{} + s.aa = S{} +} \ No newline at end of file diff --git a/testData/quickfixes/add-struct-field/complexType.go b/testData/quickfixes/add-struct-field/complexType.go new file mode 100644 index 0000000000..c64e00f996 --- /dev/null +++ b/testData/quickfixes/add-struct-field/complexType.go @@ -0,0 +1,11 @@ +package main + +type S struct { + bb interface{} + cc interface{} +} + +func main() { + s := S{} + s.aa = S{} +} \ No newline at end of file diff --git a/testData/quickfixes/add-struct-field/simple-after.go b/testData/quickfixes/add-struct-field/simple-after.go new file mode 100644 index 0000000000..f2a7f565b9 --- /dev/null +++ b/testData/quickfixes/add-struct-field/simple-after.go @@ -0,0 +1,12 @@ +package main + +type S struct { + bb interface{} + cc interface{} + aa string +} + +func main() { + s := S{} + s.aa = "aa" +} \ No newline at end of file diff --git a/testData/quickfixes/add-struct-field/simple.go b/testData/quickfixes/add-struct-field/simple.go new file mode 100644 index 0000000000..f5da0ea8a3 --- /dev/null +++ b/testData/quickfixes/add-struct-field/simple.go @@ -0,0 +1,11 @@ +package main + +type S struct { + bb interface{} + cc interface{} +} + +func main() { + s := S{} + s.aa = "aa" +} \ No newline at end of file diff --git a/testData/quickfixes/add-struct-field/withoutAssignment-after.go b/testData/quickfixes/add-struct-field/withoutAssignment-after.go new file mode 100644 index 0000000000..914ac78545 --- /dev/null +++ b/testData/quickfixes/add-struct-field/withoutAssignment-after.go @@ -0,0 +1,12 @@ +package main + +type S struct { + bb interface{} + cc interface{} + aa interface{} +} + +func main() { + s := S{} + s.aa +} \ No newline at end of file diff --git a/testData/quickfixes/add-struct-field/withoutAssignment.go b/testData/quickfixes/add-struct-field/withoutAssignment.go new file mode 100644 index 0000000000..fba0f54a07 --- /dev/null +++ b/testData/quickfixes/add-struct-field/withoutAssignment.go @@ -0,0 +1,11 @@ +package main + +type S struct { + bb interface{} + cc interface{} +} + +func main() { + s := S{} + s.aa +} \ No newline at end of file diff --git a/testData/quickfixes/add-struct-field/withoutElements-after.go b/testData/quickfixes/add-struct-field/withoutElements-after.go new file mode 100644 index 0000000000..b5143a161c --- /dev/null +++ b/testData/quickfixes/add-struct-field/withoutElements-after.go @@ -0,0 +1,10 @@ +package main + +type S struct { + aa string +} + +func main() { + s := S{} + s.aa = "aa" +} \ No newline at end of file diff --git a/testData/quickfixes/add-struct-field/withoutElements.go b/testData/quickfixes/add-struct-field/withoutElements.go new file mode 100644 index 0000000000..18a797abaa --- /dev/null +++ b/testData/quickfixes/add-struct-field/withoutElements.go @@ -0,0 +1,10 @@ +package main + +type S struct { + +} + +func main() { + s := S{} + s.aa = "aa" +} \ No newline at end of file diff --git a/tests/com/goide/quickfix/GoAddStructFieldQuickFixTest.java b/tests/com/goide/quickfix/GoAddStructFieldQuickFixTest.java new file mode 100644 index 0000000000..2cbd260e29 --- /dev/null +++ b/tests/com/goide/quickfix/GoAddStructFieldQuickFixTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2017 Sergey Ignatov, Alexander Zolotov, Florin Patan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.goide.quickfix; + +import com.goide.SdkAware; +import com.goide.inspections.unresolved.GoUnresolvedReferenceInspection; +import org.jetbrains.annotations.NotNull; + +@SdkAware +public class GoAddStructFieldQuickFixTest extends GoQuickFixTestBase { + + private static final String ADD_STRUCT_FIELD = "Add missing field"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + myFixture.enableInspections(GoUnresolvedReferenceInspection.class); + } + + @NotNull + @Override + protected String getBasePath() { + return "quickfixes/add-struct-field"; + } + + public void testSimple() { doTest(ADD_STRUCT_FIELD); } + public void testWithoutElements() { doTest(ADD_STRUCT_FIELD); } + public void testComplexType() { doTest(ADD_STRUCT_FIELD); } + public void testWithoutAssignment() { doTest(ADD_STRUCT_FIELD); } + public void testBlank() { doTestNoFix(ADD_STRUCT_FIELD, true); } +}