/*
* 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;
import com.goide.GoConstants;
import com.goide.project.GoVendoringUtil;
import com.goide.psi.GoFile;
import com.goide.psi.GoImportSpec;
import com.goide.psi.impl.imports.GoImportReference;
import com.goide.quickfix.GoDeleteImportQuickFix;
import com.goide.quickfix.GoDisableVendoringInModuleQuickFix;
import com.goide.runconfig.testing.GoTestFinder;
import com.goide.sdk.GoPackageUtil;
import com.goide.sdk.GoSdkService;
import com.goide.sdk.GoSdkUtil;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInspection.LocalQuickFixBase;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.ElementManipulators;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Set;
public class GoInvalidPackageImportInspection extends GoInspectionBase {
public static final String DELETE_ALIAS_QUICK_FIX_NAME = "Delete alias";
@Override
protected void checkFile(@NotNull GoFile file, @NotNull ProblemsHolder problemsHolder) {
Module module = ModuleUtilCore.findModuleForPsiElement(file);
VirtualFile sdkHome = GoSdkUtil.getSdkSrcDir(file.getProject(), module);
boolean supportsVendoring = GoVendoringUtil.isVendoringEnabled(module);
String sdkVersion = GoSdkService.getInstance(file.getProject()).getSdkVersion(module);
boolean supportsInternalPackages = GoVendoringUtil.supportsInternalPackages(sdkVersion);
boolean supportsInternalPackagesInSdk = sdkHome != null && GoVendoringUtil.supportsSdkInternalPackages(sdkVersion);
for (GoImportSpec importSpec : file.getImports()) {
if (importSpec.isCImport()) {
PsiElement dot = importSpec.getDot();
if (dot != null) {
problemsHolder.registerProblem(importSpec, "Cannot rename import `C`", new GoDeleteImportSpecAlias(),
new GoDeleteImportQuickFix());
}
PsiElement identifier = importSpec.getIdentifier();
if (identifier != null) {
problemsHolder.registerProblem(importSpec, "Cannot import 'builtin' package", new GoDeleteImportSpecAlias(),
new GoDeleteImportQuickFix());
}
continue;
}
PsiDirectory resolve = importSpec.getImportString().resolve();
if (resolve != null) {
if (GoPackageUtil.isBuiltinPackage(resolve)) {
problemsHolder.registerProblem(importSpec, "Cannot import 'builtin' package", new GoDeleteImportQuickFix());
}
Collection<String> packagesInDirectory = GoPackageUtil.getAllPackagesInDirectory(resolve, module, true);
if (packagesInDirectory.isEmpty()) {
problemsHolder.registerProblem(importSpec, "'" + resolve.getVirtualFile().getPath() + "' has no buildable Go source files",
new GoDeleteImportQuickFix());
continue;
}
if (!GoTestFinder.isTestFile(file) && packagesInDirectory.size() == 1 && packagesInDirectory.contains(GoConstants.MAIN)) {
problemsHolder.registerProblem(importSpec, "'" + importSpec.getPath() + "' is a program, not an importable package",
new GoDeleteImportQuickFix());
continue;
}
if (packagesInDirectory.size() > 1) {
problemsHolder.registerProblem(importSpec, "Found several packages [" + StringUtil.join(packagesInDirectory, ", ") + "] in '" +
resolve.getVirtualFile().getPath() + "'", new GoDeleteImportQuickFix());
continue;
}
VirtualFile contextFile = file.getVirtualFile();
VirtualFile resolvedFile = resolve.getVirtualFile();
boolean resolvedToSdk = sdkHome != null && VfsUtilCore.isAncestor(sdkHome, resolvedFile, false);
boolean validateInternal = supportsInternalPackages || supportsInternalPackagesInSdk && resolvedToSdk;
if (supportsVendoring || validateInternal || resolvedToSdk) {
Set<VirtualFile> sourceRoots = GoSdkUtil.getSourcesPathsToLookup(file.getProject(), module);
for (PsiReference reference : importSpec.getImportString().getReferences()) {
if (reference instanceof GoImportReference) {
String canonicalText = reference.getCanonicalText();
if (resolvedToSdk && GoConstants.TESTDATA_NAME.equals(canonicalText)) {
problemsHolder.registerProblem(importSpec, "Use of testdata package from SDK is not allowed", new GoDeleteImportQuickFix());
break;
}
else if (validateInternal && GoConstants.INTERNAL.equals(canonicalText)) {
if (GoSdkUtil.isUnreachableInternalPackage(resolvedFile, contextFile, sourceRoots)) {
problemsHolder.registerProblem(importSpec, "Use of internal package is not allowed", new GoDeleteImportQuickFix());
break;
}
}
else if (supportsVendoring && GoConstants.VENDOR.equals(canonicalText)) {
if (GoSdkUtil.isUnreachableVendoredPackage(resolvedFile, contextFile, sourceRoots)) {
problemsHolder.registerProblem(importSpec, "Use of vendored package is not allowed",
new GoDeleteImportQuickFix(), GoDisableVendoringInModuleQuickFix.create(module));
break;
}
else {
String vendoredImportPath = GoSdkUtil.getImportPath(resolve, true);
if (vendoredImportPath != null) {
problemsHolder.registerProblem(importSpec, "Must be imported as '" + vendoredImportPath + "'",
new GoReplaceImportPath(vendoredImportPath),
new GoDeleteImportQuickFix(), GoDisableVendoringInModuleQuickFix.create(module));
break;
}
}
}
}
}
}
}
else {
for (PsiReference reference : importSpec.getImportString().getReferences()) {
if (reference instanceof GoImportReference) {
if (((GoImportReference)reference).getFileReferenceSet().isAbsolutePathReference()) {
problemsHolder.registerProblem(importSpec, "Cannot import absolute path", new GoDeleteImportQuickFix());
break;
}
}
}
}
}
}
private static class GoDeleteImportSpecAlias extends LocalQuickFixBase {
protected GoDeleteImportSpecAlias() {
super(DELETE_ALIAS_QUICK_FIX_NAME);
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
GoImportSpec element = ObjectUtils.tryCast(descriptor.getPsiElement(), GoImportSpec.class);
if (element != null) {
WriteCommandAction.runWriteCommandAction(project, () -> {
PsiElement dot = element.getDot();
if (dot != null) {
dot.delete();
return;
}
PsiElement identifier = element.getIdentifier();
if (identifier != null) {
identifier.delete();
}
});
}
}
}
private static class GoReplaceImportPath extends LocalQuickFixBase implements HighPriorityAction {
@NotNull private final String myNewImportPath;
protected GoReplaceImportPath(@NotNull String newImportPath) {
super("Replace with '" + newImportPath + "'");
myNewImportPath = newImportPath;
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
GoImportSpec element = ObjectUtils.tryCast(descriptor.getPsiElement(), GoImportSpec.class);
if (element != null) {
ElementManipulators.handleContentChange(element.getImportString(), myNewImportPath);
}
}
}
}