/* * 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.psi; import com.goide.GoConstants; import com.goide.GoFileType; import com.goide.GoLanguage; import com.goide.GoTypes; import com.goide.psi.impl.GoPsiImplUtil; import com.goide.runconfig.testing.GoTestFinder; import com.goide.sdk.GoPackageUtil; import com.goide.sdk.GoSdkUtil; import com.goide.stubs.GoConstSpecStub; import com.goide.stubs.GoFileStub; import com.goide.stubs.GoVarSpecStub; import com.goide.stubs.types.*; import com.goide.util.GoUtil; import com.intellij.extapi.psi.PsiFileBase; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayFactory; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; public class GoFile extends PsiFileBase { public GoFile(@NotNull FileViewProvider viewProvider) { super(viewProvider, GoLanguage.INSTANCE); } @Nullable public String getImportPath(boolean withVendoring) { return GoSdkUtil.getImportPath(getParent(), withVendoring); } @NotNull @Override public GlobalSearchScope getResolveScope() { return GoUtil.goPathResolveScope(this); } @NotNull @Override public SearchScope getUseScope() { return GoUtil.goPathUseScope(this, true); } @Nullable public GoPackageClause getPackage() { return CachedValuesManager.getCachedValue(this, () -> { GoFileStub stub = getStub(); if (stub != null) { StubElement<GoPackageClause> packageClauseStub = stub.getPackageClauseStub(); return CachedValueProvider.Result.create(packageClauseStub != null ? packageClauseStub.getPsi() : null, this); } return CachedValueProvider.Result.create(findChildByClass(GoPackageClause.class), this); }); } @Nullable public GoImportList getImportList() { return findChildByClass(GoImportList.class); } @Nullable public String getBuildFlags() { GoFileStub stub = getStub(); if (stub != null) { return stub.getBuildFlags(); } // https://code.google.com/p/go/source/browse/src/pkg/go/build/build.go?r=2449e85a115014c3d9251f86d499e5808141e6bc#790 Collection<String> buildFlags = ContainerUtil.newArrayList(); int buildFlagLength = GoConstants.BUILD_FLAG.length(); for (PsiComment comment : getCommentsToConsider(this)) { String commentText = StringUtil.trimStart(comment.getText(), "//").trim(); if (commentText.startsWith(GoConstants.BUILD_FLAG) && commentText.length() > buildFlagLength && StringUtil.isWhiteSpace(commentText.charAt(buildFlagLength))) { ContainerUtil.addIfNotNull(buildFlags, StringUtil.nullize(commentText.substring(buildFlagLength).trim(), true)); } } return !buildFlags.isEmpty() ? StringUtil.join(buildFlags, "|") : null; } @NotNull public List<GoFunctionDeclaration> getFunctions() { return CachedValuesManager.getCachedValue(this, () -> { GoFileStub stub = getStub(); List<GoFunctionDeclaration> functions = stub != null ? getChildrenByType(stub, GoTypes.FUNCTION_DECLARATION, GoFunctionDeclarationStubElementType.ARRAY_FACTORY) : GoPsiImplUtil.goTraverser().children(this).filter(GoFunctionDeclaration.class).toList(); return CachedValueProvider.Result.create(functions, this); }); } @NotNull public List<GoMethodDeclaration> getMethods() { return CachedValuesManager.getCachedValue(this, () -> { StubElement<GoFile> stub = getStub(); List<GoMethodDeclaration> calc = stub != null ? getChildrenByType(stub, GoTypes.METHOD_DECLARATION, GoMethodDeclarationStubElementType.ARRAY_FACTORY) : GoPsiImplUtil.goTraverser().children(this).filter(GoMethodDeclaration.class).toList(); return CachedValueProvider.Result.create(calc, this); }); } @NotNull public List<GoTypeSpec> getTypes() { return CachedValuesManager.getCachedValue(this, () -> { StubElement<GoFile> stub = getStub(); List<GoTypeSpec> types = stub != null ? getChildrenByType(stub, GoTypes.TYPE_SPEC, GoTypeSpecStubElementType.ARRAY_FACTORY) : calcTypes(); return CachedValueProvider.Result.create(types, this); }); } @NotNull public List<GoImportSpec> getImports() { return CachedValuesManager.getCachedValue(this, () -> { StubElement<GoFile> stub = getStub(); List<GoImportSpec> imports = stub != null ? getChildrenByType(stub, GoTypes.IMPORT_SPEC, GoImportSpecStubElementType.ARRAY_FACTORY) : calcImports(); return CachedValueProvider.Result.create(imports, this); }); } public GoImportSpec addImport(String path, String alias) { GoImportList importList = getImportList(); if (importList != null) { return importList.addImport(path, alias); } return null; } /** * @return map like { import path -> import spec } for file */ @NotNull public Map<String, GoImportSpec> getImportedPackagesMap() { return CachedValuesManager.getCachedValue(this, () -> { Map<String, GoImportSpec> map = ContainerUtil.newHashMap(); for (GoImportSpec spec : getImports()) { if (!spec.isForSideEffects()) { String importPath = spec.getPath(); if (StringUtil.isNotEmpty(importPath)) { map.put(importPath, spec); } } } return CachedValueProvider.Result.create(map, this); }); } /** * @return map like { local package name, maybe alias -> import spec } for file */ @NotNull public MultiMap<String, GoImportSpec> getImportMap() { return CachedValuesManager.getCachedValue(this, () -> { MultiMap<String, GoImportSpec> map = MultiMap.createLinked(); List<Object> dependencies = ContainerUtil.newArrayList(this); Module module = ModuleUtilCore.findModuleForPsiElement(this); for (GoImportSpec spec : getImports()) { String alias = spec.getAlias(); if (alias != null) { map.putValue(alias, spec); continue; } if (spec.isDot()) { map.putValue(".", spec); continue; } GoImportString string = spec.getImportString(); PsiDirectory dir = string.resolve(); // todo[zolotov]: implement package modification tracker ContainerUtil.addIfNotNull(dependencies, dir); Collection<String> packagesInDirectory = GoPackageUtil.getAllPackagesInDirectory(dir, module, true); if (!packagesInDirectory.isEmpty()) { for (String packageNames : packagesInDirectory) { if (!StringUtil.isEmpty(packageNames)) { map.putValue(packageNames, spec); } } } else { String key = spec.getLocalPackageName(); if (!StringUtil.isEmpty(key)) { map.putValue(key, spec); } } } return CachedValueProvider.Result.create(map, ArrayUtil.toObjectArray(dependencies)); }); } @NotNull public List<GoVarDefinition> getVars() { return CachedValuesManager.getCachedValue(this, () -> { List<GoVarDefinition> result; StubElement<GoFile> stub = getStub(); if (stub != null) { result = ContainerUtil.newArrayList(); List<GoVarSpec> varSpecs = getChildrenByType(stub, GoTypes.VAR_SPEC, GoVarSpecStubElementType.ARRAY_FACTORY); for (GoVarSpec spec : varSpecs) { GoVarSpecStub specStub = spec.getStub(); if (specStub == null) continue; result.addAll(getChildrenByType(specStub, GoTypes.VAR_DEFINITION, GoVarDefinitionStubElementType.ARRAY_FACTORY)); } } else { result = calcVars(); } return CachedValueProvider.Result.create(result, this); }); } @NotNull public List<GoConstDefinition> getConstants() { return CachedValuesManager.getCachedValue(this, () -> { StubElement<GoFile> stub = getStub(); List<GoConstDefinition> result; if (stub != null) { result = ContainerUtil.newArrayList(); List<GoConstSpec> constSpecs = getChildrenByType(stub, GoTypes.CONST_SPEC, GoConstSpecStubElementType.ARRAY_FACTORY); for (GoConstSpec spec : constSpecs) { GoConstSpecStub specStub = spec.getStub(); if (specStub == null) continue; result.addAll(getChildrenByType(specStub, GoTypes.CONST_DEFINITION, GoConstDefinitionStubElementType.ARRAY_FACTORY)); } } else { result = calcConsts(); } return CachedValueProvider.Result.create(result, this); }); } @NotNull private List<GoTypeSpec> calcTypes() { return GoPsiImplUtil.goTraverser().children(this).filter(GoTypeDeclaration.class).flatten(GoTypeDeclaration::getTypeSpecList).toList(); } @NotNull private List<GoImportSpec> calcImports() { GoImportList list = getImportList(); if (list == null) return ContainerUtil.emptyList(); List<GoImportSpec> result = ContainerUtil.newArrayList(); for (GoImportDeclaration declaration : list.getImportDeclarationList()) { result.addAll(declaration.getImportSpecList()); } return result; } @NotNull private List<GoVarDefinition> calcVars() { return GoPsiImplUtil.goTraverser().children(this).filter(GoVarDeclaration.class) .flatten(GoVarDeclaration::getVarSpecList) .flatten(GoVarSpec::getVarDefinitionList).toList(); } @NotNull private List<GoConstDefinition> calcConsts() { return GoPsiImplUtil.goTraverser().children(this).filter(GoConstDeclaration.class) .flatten(GoConstDeclaration::getConstSpecList) .flatten(GoConstSpec::getConstDefinitionList).toList(); } @NotNull @Override public FileType getFileType() { return GoFileType.INSTANCE; } public boolean hasMainFunction() { // todo create a map for faster search List<GoFunctionDeclaration> functions = getFunctions(); for (GoFunctionDeclaration function : functions) { if (GoConstants.MAIN.equals(function.getName())) { return true; } } return false; } @Nullable public String getPackageName() { return CachedValuesManager.getCachedValue(this, () -> { GoFileStub stub = getStub(); if (stub != null) { return CachedValueProvider.Result.create(stub.getPackageName(), this); } GoPackageClause packageClause = getPackage(); return CachedValueProvider.Result.create(packageClause != null ? packageClause.getName() : null, this); }); } public String getCanonicalPackageName() { String packageName = getPackageName(); if (StringUtil.isNotEmpty(packageName) && GoTestFinder.isTestFile(this)) { return StringUtil.trimEnd(packageName, GoConstants.TEST_SUFFIX); } return packageName; } @Nullable @Override public GoFileStub getStub() { //noinspection unchecked return (GoFileStub)super.getStub(); } public boolean hasCPathImport() { return getImportedPackagesMap().containsKey(GoConstants.C_PATH); } public void deleteImport(@NotNull GoImportSpec importSpec) { GoImportDeclaration importDeclaration = PsiTreeUtil.getParentOfType(importSpec, GoImportDeclaration.class); assert importDeclaration != null; PsiElement elementToDelete = importDeclaration.getImportSpecList().size() == 1 ? importDeclaration : importSpec; elementToDelete.delete(); } @NotNull private static <E extends PsiElement> List<E> getChildrenByType(@NotNull StubElement<? extends PsiElement> stub, IElementType elementType, ArrayFactory<E> f) { return Arrays.asList(stub.getChildrenByType(elementType, f)); } @NotNull private static Collection<PsiComment> getCommentsToConsider(@NotNull GoFile file) { Collection<PsiComment> commentsToConsider = ContainerUtil.newArrayList(); PsiElement child = file.getFirstChild(); int lastEmptyLineOffset = 0; while (child != null) { if (child instanceof PsiComment) { commentsToConsider.add((PsiComment)child); } else if (child instanceof PsiWhiteSpace) { if (StringUtil.countChars(child.getText(), '\n') > 1) { lastEmptyLineOffset = child.getTextRange().getStartOffset(); } } else { break; } child = child.getNextSibling(); } int finalLastEmptyLineOffset = lastEmptyLineOffset; return ContainerUtil.filter(commentsToConsider, comment -> comment.getTextRange().getStartOffset() < finalLastEmptyLineOffset); } }