/* * Copyright 2013-2016 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.GoTypes; import com.goide.codeInsight.imports.GoImportPackageQuickFix; import com.goide.inspections.GoInspectionBase; import com.goide.psi.*; import com.goide.psi.impl.GoReference; import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.psi.ResolveResult; 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.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; import static com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING; import static com.intellij.codeInspection.ProblemHighlightType.LIKE_UNKNOWN_SYMBOL; public class GoUnresolvedReferenceInspection extends GoInspectionBase { @NotNull @Override protected GoVisitor buildGoVisitor(@NotNull ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { return new GoVisitor() { @Override public void visitFieldName(@NotNull GoFieldName o) { super.visitFieldName(o); PsiElement resolve = o.resolve(); if (resolve == null) { PsiElement id = o.getIdentifier(); holder.registerProblem(id, "Unknown field <code>#ref</code> in struct literal #loc", LIKE_UNKNOWN_SYMBOL); } } @Override public void visitReferenceExpression(@NotNull GoReferenceExpression o) { super.visitReferenceExpression(o); GoReference reference = o.getReference(); GoReferenceExpression qualifier = o.getQualifier(); GoReference qualifierRef = qualifier != null ? qualifier.getReference() : null; PsiElement qualifierResolve = qualifierRef != null ? qualifierRef.resolve() : null; if (qualifier != null && qualifierResolve == null) return; ResolveResult[] results = reference.multiResolve(false); PsiElement id = o.getIdentifier(); String name = id.getText(); if (results.length > 1) { holder.registerProblem(id, "Ambiguous reference " + "'" + name + "'", GENERIC_ERROR_OR_WARNING); } else if (reference.resolve() == null) { LocalQuickFix[] fixes = LocalQuickFix.EMPTY_ARRAY; if (isProhibited(o, qualifier)) { fixes = createImportPackageFixes(o, reference, holder.isOnTheFly()); } else if (holder.isOnTheFly()) { boolean canBeLocal = PsiTreeUtil.getParentOfType(o, GoBlock.class) != null; List<LocalQuickFix> fixesList = ContainerUtil.newArrayList(new GoIntroduceGlobalVariableFix(id, name)); if (canBeLocal) { fixesList.add(new GoIntroduceLocalVariableFix(id, name)); } PsiElement parent = o.getParent(); if (o.getReadWriteAccess() == ReadWriteAccessDetector.Access.Read) { fixesList.add(new GoIntroduceGlobalConstantFix(id, name)); if (canBeLocal) { fixesList.add(new GoIntroduceLocalConstantFix(id, name)); } } else if (canBeLocal) { PsiElement grandParent = parent.getParent(); if (grandParent instanceof GoAssignmentStatement) { fixesList.add(new GoReplaceAssignmentWithDeclarationQuickFix(grandParent)); } else if (parent instanceof GoRangeClause || parent instanceof GoRecvStatement) { fixesList.add(new GoReplaceAssignmentWithDeclarationQuickFix(parent)); } } if (parent instanceof GoCallExpr && PsiTreeUtil.getParentOfType(o, GoConstDeclaration.class) == null) { fixesList.add(new GoIntroduceFunctionFix(parent, name)); } fixes = fixesList.toArray(new LocalQuickFix[fixesList.size()]); } holder.registerProblem(id, "Unresolved reference " + "'" + name + "'", LIKE_UNKNOWN_SYMBOL, fixes); } } @Override public void visitImportSpec(@NotNull GoImportSpec o) { if (o.isCImport()) return; GoImportString string = o.getImportString(); if (string.getTextLength() < 2) return; PsiReference[] references = string.getReferences(); for (PsiReference reference : references) { if (reference instanceof FileReference) { ResolveResult[] resolveResults = ((FileReference)reference).multiResolve(false); if (resolveResults.length == 0) { ProblemHighlightType type = reference.getRangeInElement().isEmpty() ? GENERIC_ERROR_OR_WARNING : LIKE_UNKNOWN_SYMBOL; holder.registerProblem(reference, ProblemsHolder.unresolvedReferenceMessage(reference), type); } } } } @Override public void visitLabelRef(@NotNull GoLabelRef o) { PsiReference reference = o.getReference(); String name = o.getText(); if (reference.resolve() == null) { holder.registerProblem(o, "Unresolved label " + "'" + name + "'", LIKE_UNKNOWN_SYMBOL); } } @Override public void visitTypeReferenceExpression(@NotNull GoTypeReferenceExpression o) { super.visitTypeReferenceExpression(o); PsiReference reference = o.getReference(); GoTypeReferenceExpression qualifier = o.getQualifier(); PsiElement qualifierResolve = qualifier != null ? qualifier.resolve() : null; if (qualifier != null && qualifierResolve == null) return; if (reference.resolve() == null) { PsiElement id = o.getIdentifier(); String name = id.getText(); boolean isProhibited = isProhibited(o, qualifier); LocalQuickFix[] fixes = LocalQuickFix.EMPTY_ARRAY; if (isProhibited) { fixes = createImportPackageFixes(o, reference, holder.isOnTheFly()); } else if (holder.isOnTheFly()) { fixes = new LocalQuickFix[]{new GoIntroduceTypeFix(id, name)}; } holder.registerProblem(id, "Unresolved type " + "'" + name + "'", LIKE_UNKNOWN_SYMBOL, fixes); } } }; } @NotNull private static LocalQuickFix[] createImportPackageFixes(@NotNull PsiElement target, @NotNull PsiReference reference, boolean onTheFly) { if (onTheFly) { GoImportPackageQuickFix importFix = new GoImportPackageQuickFix(reference); if (importFix.isAvailable(target.getProject(), target.getContainingFile(), target, target)) { return new LocalQuickFix[]{importFix}; } } else { List<String> packagesToImport = GoImportPackageQuickFix.getImportPathVariantsToImport(reference.getCanonicalText(), target); if (!packagesToImport.isEmpty()) { Collection<LocalQuickFix> result = ContainerUtil.newArrayList(); for (String importPath : packagesToImport) { GoImportPackageQuickFix importFix = new GoImportPackageQuickFix(target, importPath); if (importFix.isAvailable(target.getProject(), target.getContainingFile(), target, target)) { result.add(importFix); } } return result.toArray(new LocalQuickFix[result.size()]); } } return LocalQuickFix.EMPTY_ARRAY; } private static boolean isProhibited(@NotNull GoCompositeElement o, @Nullable GoCompositeElement qualifier) { ASTNode next = FormatterUtil.getNextNonWhitespaceSibling(o.getNode()); boolean isDot = next != null && next.getElementType() == GoTypes.DOT; return isDot || qualifier != null; } }