/* * Copyright 2012-2014 Sergey Ignatov * * 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 org.intellij.erlang.psi.impl; import com.intellij.extapi.psi.PsiFileBase; import com.intellij.ide.scratch.ScratchFileType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import gnu.trove.THashMap; import org.intellij.erlang.ErlangFileType; import org.intellij.erlang.ErlangLanguage; import org.intellij.erlang.ErlangTypes; import org.intellij.erlang.parser.ErlangParserUtil; import org.intellij.erlang.psi.*; import org.intellij.erlang.stubs.ErlangCallbackSpecStub; import org.intellij.erlang.stubs.ErlangFileStub; import org.intellij.erlang.stubs.types.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import static java.util.Collections.*; import static org.intellij.erlang.psi.impl.ErlangPsiImplUtil.*; public class ErlangFileImpl extends PsiFileBase implements ErlangFile, PsiNameIdentifierOwner { private final CachedValue<ErlangModule> myModuleValue = createCachedValue(new ValueProvider<ErlangModule>() { @Nullable @Override protected ErlangModule computeValue() { return calcModule(); } }); private final CachedValue<List<ErlangRule>> myRulesValue = createCachedValue(new ValueProvider<List<ErlangRule>>() { @NotNull @Override protected List<ErlangRule> computeValue() { return unmodifiableList(calcRules()); } }); private final CachedValue<List<ErlangFunction>> myFunctionValue = createCachedValue(new ValueProvider<List<ErlangFunction>>() { @NotNull @Override public List<ErlangFunction> computeValue() { return unmodifiableList(calcFunctions()); } }); private final CachedValue<List<ErlangImportFunction>> myImportValue = createCachedValue(new ValueProvider<List<ErlangImportFunction>>() { @NotNull @Override public List<ErlangImportFunction> computeValue() { return unmodifiableList(calcImports()); } }); private final CachedValue<Set<ErlangFunction>> myExportedFunctionValue = createCachedValue(new ValueProvider<Set<ErlangFunction>>() { @NotNull @Override public Set<ErlangFunction> computeValue() { return unmodifiableSet(calcExportedFunctions()); } }); private final CachedValue<List<ErlangAttribute>> myAttributeValue = createCachedValue(new ValueProvider<List<ErlangAttribute>>() { @NotNull @Override public List<ErlangAttribute> computeValue() { return unmodifiableList(calcAttributes()); } }); private final CachedValue<List<ErlangRecordDefinition>> myRecordValue = createCachedValue(new ValueProvider<List<ErlangRecordDefinition>>() { @NotNull @Override public List<ErlangRecordDefinition> computeValue() { return unmodifiableList(calcRecords()); } }); private final CachedValue<List<ErlangInclude>> myIncludeValue = createCachedValue(new ValueProvider<List<ErlangInclude>>() { @NotNull @Override public List<ErlangInclude> computeValue() { return unmodifiableList(calcIncludes()); } }); private final CachedValue<List<ErlangIncludeLib>> myIncludeLibValue = createCachedValue(new ValueProvider<List<ErlangIncludeLib>>() { @NotNull @Override public List<ErlangIncludeLib> computeValue() { return unmodifiableList(calcIncludeLibs()); } }); private final CachedValue<MultiMap<String, ErlangFunction>> myFunctionsMap = createCachedValue(new ValueProvider<MultiMap<String, ErlangFunction>>() { @NotNull @Override public MultiMap<String, ErlangFunction> computeValue() { MultiMap<String, ErlangFunction> map = new MultiMap<>(); for (ErlangFunction function : getFunctions()) { map.putValue(function.getName(), function); } return map; } }); private final CachedValue<MultiMap<String, ErlangImportFunction>> myImportsMap = createCachedValue(new ValueProvider<MultiMap<String, ErlangImportFunction>>() { @NotNull @Override public MultiMap<String, ErlangImportFunction> computeValue() { MultiMap<String, ErlangImportFunction> map = new MultiMap<>(); for (ErlangImportFunction importFunction : getImportedFunctions()) { map.putValue(ErlangPsiImplUtil.getName(importFunction), importFunction); } return map; } }); private final CachedValue<Map<String, ErlangRecordDefinition>> myRecordsMap = createCachedValue(new ValueProvider<Map<String, ErlangRecordDefinition>>() { @NotNull @Override public Map<String, ErlangRecordDefinition> computeValue() { Map<String, ErlangRecordDefinition> map = new THashMap<>(); for (ErlangRecordDefinition record : getRecords()) { String recordName = record.getName(); if (!map.containsKey(recordName)) { map.put(recordName, record); } } return map; } }); private final CachedValue<List<ErlangMacrosDefinition>> myMacrosValue = createCachedValue(new ValueProvider<List<ErlangMacrosDefinition>>() { @NotNull @Override public List<ErlangMacrosDefinition> computeValue() { return unmodifiableList(calcMacroses()); } }); private final CachedValue<Map<String, ErlangMacrosDefinition>> myMacrosesMap = createCachedValue(new ValueProvider<Map<String, ErlangMacrosDefinition>>() { @NotNull @Override public Map<String, ErlangMacrosDefinition> computeValue() { Map<String, ErlangMacrosDefinition> map = new THashMap<>(); for (ErlangMacrosDefinition macros : getMacroses()) { String macrosName = ErlangPsiImplUtil.getName(macros); if (!map.containsKey(macrosName)) { map.put(macrosName, macros); } } return map; } }); private final CachedValue<List<ErlangTypeDefinition>> myTypeValue = createCachedValue(new ValueProvider<List<ErlangTypeDefinition>>() { @NotNull @Override public List<ErlangTypeDefinition> computeValue() { return unmodifiableList(calcTypes()); } }); private final CachedValue<Map<String, ErlangTypeDefinition>> myTypeMap = createCachedValue(new ValueProvider<Map<String, ErlangTypeDefinition>>() { @NotNull @Override public Map<String, ErlangTypeDefinition> computeValue() { Map<String, ErlangTypeDefinition> map = new THashMap<>(); for (ErlangTypeDefinition type : getTypes()) { String mName = type.getName(); if (!map.containsKey(mName)) { map.put(mName, type); } } return map; } }); private final CachedValue<Map<String, ErlangCallbackSpec>> myCallbackMap = createCachedValue(new ValueProvider<Map<String, ErlangCallbackSpec>>() { @NotNull @Override public Map<String, ErlangCallbackSpec> computeValue() { return unmodifiableMap(calcCallbacks()); } }); private final CachedValue<List<ErlangBehaviour>> myBehavioursValue = createCachedValue(new ValueProvider<List<ErlangBehaviour>>() { @NotNull @Override public List<ErlangBehaviour> computeValue() { return unmodifiableList(calcBehaviours()); } }); private final CachedValue<Collection<ErlangCallbackFunction>> myOptionalCallbacks = createCachedValue(new ValueProvider<Collection<ErlangCallbackFunction>>() { @NotNull @Override public Collection<ErlangCallbackFunction> computeValue() { return unmodifiableCollection(calcOptionalCallbacks()); } }); private final CachedValue<List<ErlangSpecification>> mySpecificationsValue = createCachedValue(new ValueProvider<List<ErlangSpecification>>() { @NotNull @Override public List<ErlangSpecification> computeValue() { return unmodifiableList(calcSpecifications()); } }); private final CachedValue<Boolean> myExportAll = createCachedValue(new ValueProvider<Boolean>() { @NotNull @Override public Boolean computeValue() { return calcExportAll(); } }); private final CachedValue<Boolean> myNoAutoImportAll = createCachedValue(new ValueProvider<Boolean>() { @NotNull @Override public Boolean computeValue() { return calcNoAutoImportAll(); } }); private final CachedValue<Set<String>> myExportedFunctionsSignatures = createCachedValue(new ValueProvider<Set<String>>() { @NotNull @Override public Set<String> computeValue() { return unmodifiableSet(calcExportedSignatures()); } }); private final CachedValue<Set<String>> myNoAutoImportFunctionsSignatures = createCachedValue(new ValueProvider<Set<String>>() { @NotNull @Override public Set<String> computeValue() { return unmodifiableSet(calcNoAutoImportSignatures()); } }); public ErlangFileImpl(@NotNull FileViewProvider viewProvider) { super(viewProvider, ErlangLanguage.INSTANCE); } @Override public PsiElement setName(@NotNull String name) throws IncorrectOperationException { String nameWithoutExtension = FileUtil.getNameWithoutExtension(name); for (ErlangAttribute moduleAttributes : getAttributes()) { ErlangModule module = moduleAttributes.getModule(); if (module != null) { // todo: use module with dependencies scope if (!DumbService.isDumb(getProject())) { Query<PsiReference> search = ReferencesSearch.search(module, GlobalSearchScope.allScope(module.getProject())); for (PsiReference psiReference : search) { psiReference.handleElementRename(nameWithoutExtension); } } module.setName(nameWithoutExtension); } } return super.setName(name); } @NotNull @Override public FileType getFileType() { FileType fileType = getViewProvider().getFileType(); if ((fileType instanceof ScratchFileType)) return fileType; if (!(fileType instanceof ErlangFileType) && ApplicationManager.getApplication().isUnitTestMode()) { return getFileTypeForTests(); } return fileType; } @NotNull private FileType getFileTypeForTests() { // when in unit test mode fileViewProvider gets replaced by a mock and we don't get filetype set correctly assert ApplicationManager.getApplication().isUnitTestMode() : "This method should only be called in unit tests"; String extension = PathUtil.getFileExtension(getName()); for (ErlangFileType type : ErlangFileType.TYPES) { for (String defaultExtension : type.getDefaultExtensions()) { if (StringUtil.equalsIgnoreCase(defaultExtension, extension)) { return type; } } } return ErlangFileType.MODULE; } @Nullable @Override public ErlangFileStub getStub() { StubElement stub = super.getStub(); return (ErlangFileStub) stub; } @Nullable @Override public ErlangModule getModule() { return myModuleValue.getValue(); } @Nullable private ErlangModule calcModule() { ErlangFileStub stub = getStub(); if (stub != null) { return ArrayUtil.getFirstElement(stub.getChildrenByType(ErlangTypes.ERL_MODULE, ErlangModuleStubElementType.ARRAY_FACTORY)); } for (ErlangAttribute attribute : getAttributes()) { ErlangModule module = attribute.getModule(); if (module != null) { return module; } } return null; } @Override public boolean isExported(@NotNull String signature) { if (isExportedAll()) return true; return myExportedFunctionsSignatures.getValue().contains(signature); } @Override public boolean isNoAutoImport(@NotNull String name, int arity) { if (isNoAutoImportAll()) return true; return myNoAutoImportFunctionsSignatures.getValue().contains(name + "/" + arity); } @NotNull private Set<String> calcExportedSignatures() { Set<String> result = ContainerUtil.newHashSet(); for (ErlangAttribute attribute : getAttributes()) { ErlangExport export = attribute.getExport(); ErlangExportFunctions exportFunctions = export != null ? export.getExportFunctions() : null; if (exportFunctions == null) continue; List<ErlangExportFunction> list = exportFunctions.getExportFunctionList(); for (ErlangExportFunction exportFunction : list) { PsiElement integer = exportFunction.getInteger(); if (integer == null) continue; String s = ErlangPsiImplUtil.getExportFunctionName(exportFunction) + "/" + integer.getText(); result.add(s); } } return result; } @NotNull private List<ErlangExpression> getCompileDirectiveExpressions() { List<ErlangExpression> result = ContainerUtil.newArrayList(); for (ErlangAttribute attribute : getAttributes()) { ErlangAtomAttribute atomAttribute = attribute.getAtomAttribute(); if (atomAttribute != null && "compile".equals(atomAttribute.getName()) && atomAttribute.getAttrVal() != null) { result.addAll(atomAttribute.getAttrVal().getExpressionList()); } } return result; } @NotNull private Set<String> calcNoAutoImportSignatures() { Set<String> result = ContainerUtil.newHashSet(); for (ErlangExpression expression : getCompileDirectiveExpressions()) { if (expression instanceof ErlangListExpression) { for (ErlangExpression tuple : ((ErlangListExpression) expression).getExpressionList()) { if (tuple instanceof ErlangTupleExpression) { result.addAll(getNoAutoImportFunctionSignaturesFromTuple((ErlangTupleExpression) tuple)); } } } else if (expression instanceof ErlangTupleExpression) { result.addAll(getNoAutoImportFunctionSignaturesFromTuple((ErlangTupleExpression) expression)); } } return result; } @NotNull private static Set<String> getNoAutoImportFunctionSignaturesFromTuple(@Nullable ErlangTupleExpression tupleExpression) { final Set<String> result = ContainerUtil.newHashSet(); if (tupleExpression == null || tupleExpression.getExpressionList().size() != 2) return result; ErlangExpression first = ContainerUtil.getFirstItem(tupleExpression.getExpressionList()); ErlangExpression second = ContainerUtil.getLastItem(tupleExpression.getExpressionList()); if (!(first instanceof ErlangMaxExpression) || !(second instanceof ErlangListExpression) || !"no_auto_import".equals(getAtomName((ErlangMaxExpression) first))) { return result; } second.accept(new ErlangRecursiveVisitor() { @Override public void visitAtomWithArityExpression(@NotNull ErlangAtomWithArityExpression o) { result.add(createFunctionPresentation(o)); } @Override public void visitTupleExpression(@NotNull ErlangTupleExpression o) { List<ErlangExpression> exprs = o.getExpressionList(); if (exprs.size() != 2) return; String functionName = getAtomName(ObjectUtils.tryCast(exprs.get(0), ErlangMaxExpression.class)); int functionArity = getArity(ObjectUtils.tryCast(exprs.get(1), ErlangMaxExpression.class)); if (functionName == null || functionArity == -1) return; result.add(createFunctionPresentation(functionName, functionArity)); } }); return result; } @Override public boolean isExportedAll() { //TODO do we use stubs? ErlangFileStub stub = getStub(); if (stub != null) { return stub.isExportAll(); } return myExportAll.getValue(); } private boolean containsCompileDirectiveWithOption(@NotNull String option) { for (ErlangExpression expression : getCompileDirectiveExpressions()) { if (expression instanceof ErlangListExpression) { for (ErlangExpression e : ((ErlangListExpression) expression).getExpressionList()) { if (e instanceof ErlangMaxExpression && option.equals(getAtomName((ErlangMaxExpression) e))) { return true; } } } else if (expression instanceof ErlangMaxExpression && option.equals(getAtomName((ErlangMaxExpression) expression))) { return true; } } return false; } @Override public boolean isNoAutoImportAll() { return myNoAutoImportAll.getValue(); } @Override public boolean isBehaviour() { ErlangFileStub stub = getStub(); if (stub != null) return stub.isBehaviour(); ErlangFunction behaviourInfo = getFunction("behaviour_info", 1); return behaviourInfo != null && behaviourInfo.isExported() || !getCallbackMap().isEmpty(); } private boolean calcNoAutoImportAll() { return containsCompileDirectiveWithOption("no_auto_import"); } private boolean calcExportAll() { return containsCompileDirectiveWithOption("export_all"); } @NotNull @Override public List<ErlangRule> getRules() { return myRulesValue.getValue(); } @NotNull @Override public List<ErlangAttribute> getAttributes() { return myAttributeValue.getValue(); } @Nullable @Override public ErlangCallbackSpec getCallbackByName(@NotNull String fullName) { return getCallbackMap().get(fullName); } @NotNull @Override public Map<String, ErlangCallbackSpec> getCallbackMap() { //TODO do we use stubs? ErlangFileStub stub = getStub(); if (stub != null) { Map<String, ErlangCallbackSpec> callbacksMap = new LinkedHashMap<>(); for (StubElement child : stub.getChildrenStubs()) { if (child instanceof ErlangCallbackSpecStub) { String name = ((ErlangCallbackSpecStub) child).getName(); int arity = ((ErlangCallbackSpecStub) child).getArity(); callbacksMap.put(name + "/" + arity, ((ErlangCallbackSpecStub) child).getPsi()); } } return callbacksMap; } return myCallbackMap.getValue(); } @NotNull private Map<String, ErlangCallbackSpec> calcCallbacks() { Map<String, ErlangCallbackSpec> callbacksMap = new LinkedHashMap<>(); for (ErlangAttribute a : getAttributes()) { ErlangCallbackSpec spec = a.getCallbackSpec(); if (spec != null) { String name = ErlangPsiImplUtil.getCallbackSpecName(spec); int arity = ErlangPsiImplUtil.getCallBackSpecArguments(spec).size(); callbacksMap.put(name + "/" + arity, spec); } } return callbacksMap; } @NotNull @Override public List<ErlangFunction> getFunctions() { return myFunctionValue.getValue(); } @NotNull @Override public Collection<ErlangFunction> getExportedFunctions() { return myExportedFunctionValue.getValue(); } private Set<ErlangFunction> calcExportedFunctions() { return ContainerUtil.map2SetNotNull(getFunctions(), f -> f.isExported() ? f : null); } @Nullable @Override public ErlangFunction getFunction(@NotNull String name, int argsCount) { MultiMap<String, ErlangFunction> value = myFunctionsMap.getValue(); return getFunctionFromMap(value, name, argsCount); } @Override @NotNull public Collection<ErlangFunction> getFunctionsByName(@NotNull String name) { return myFunctionsMap.getValue().get(name); } @Nullable private static ErlangFunction getFunctionFromMap(MultiMap<String, ErlangFunction> value, String name, final int argsCount) { Collection<ErlangFunction> candidates = value.get(name); return ContainerUtil.getFirstItem(ContainerUtil.filter(candidates, erlangFunction -> erlangFunction.getArity() == argsCount)); } @NotNull @Override public List<ErlangRecordDefinition> getRecords() { return myRecordValue.getValue(); } @NotNull @Override public List<ErlangTypeDefinition> getTypes() { return myTypeValue.getValue(); } private List<ErlangTypeDefinition> calcTypes() { return calcChildren(ErlangTypeDefinition.class, ErlangTypes.ERL_TYPE_DEFINITION, ErlangTypeDefinitionElementType.ARRAY_FACTORY); } @Override public ErlangTypeDefinition getType(@NotNull String name) { return myTypeMap.getValue().get(name); } @NotNull @Override public List<ErlangMacrosDefinition> getMacroses() { return myMacrosValue.getValue(); } private List<ErlangMacrosDefinition> calcMacroses() { return calcChildren(ErlangMacrosDefinition.class, ErlangTypes.ERL_MACROS_DEFINITION, ErlangMacrosDefinitionElementType.ARRAY_FACTORY); } @Override public ErlangMacrosDefinition getMacros(@NotNull String name) { return myMacrosesMap.getValue().get(name); } private List<ErlangRecordDefinition> calcRecords() { return calcChildren(ErlangRecordDefinition.class, ErlangTypes.ERL_RECORD_DEFINITION, ErlangRecordDefinitionElementType.ARRAY_FACTORY); } @NotNull @Override public List<ErlangInclude> getIncludes() { return myIncludeValue.getValue(); } @NotNull @Override public List<ErlangIncludeLib> getIncludeLibs() { return myIncludeLibValue.getValue(); } private List<ErlangInclude> calcIncludes() { return calcChildren(ErlangInclude.class, ErlangTypes.ERL_INCLUDE, ErlangIncludeElementType.ARRAY_FACTORY); } private List<ErlangIncludeLib> calcIncludeLibs() { return calcChildren(ErlangIncludeLib.class, ErlangTypes.ERL_INCLUDE_LIB, ErlangIncludeLibElementType.ARRAY_FACTORY); } @NotNull @Override public List<ErlangBehaviour> getBehaviours() { return myBehavioursValue.getValue(); } private List<ErlangBehaviour> calcBehaviours() { ErlangFileStub stub = getStub(); if (stub != null) { return getChildrenByType(stub, ErlangTypes.ERL_BEHAVIOUR, ErlangBehaviourStubElementType.ARRAY_FACTORY); } return ContainerUtil.mapNotNull(getAttributes(), ErlangAttribute::getBehaviour); } @NotNull @Override public Collection<ErlangCallbackFunction> getOptionalCallbacks() { return myOptionalCallbacks.getValue(); } @NotNull private Collection<ErlangCallbackFunction> calcOptionalCallbacks() { ErlangFileStub stub = getStub(); if (stub != null) { return getChildrenByType(stub, ErlangTypes.ERL_CALLBACK_FUNCTION, ErlangCallbackFunctionStubElementType.ARRAY_FACTORY); } List<ErlangCallbackFunction> optionalCallbacks = ContainerUtil.newArrayList(); for (ErlangAttribute attr : getAttributes()) { ErlangOptionalCallbacks callbacks = attr.getOptionalCallbacks(); ErlangOptionalCallbackFunctions opts = callbacks != null ? callbacks.getOptionalCallbackFunctions() : null; optionalCallbacks.addAll(opts != null ? opts.getCallbackFunctionList() : ContainerUtil.<ErlangCallbackFunction>emptyList()); } return optionalCallbacks; } @NotNull @Override public List<ErlangSpecification> getSpecifications() { return mySpecificationsValue.getValue(); } private List<ErlangSpecification> calcSpecifications() { ErlangFileStub stub = getStub(); if (stub != null) { return getChildrenByType(stub, ErlangTypes.ERL_SPECIFICATION, ErlangSpecificationElementType.ARRAY_FACTORY); } return ContainerUtil.mapNotNull(getAttributes(), ErlangAttribute::getSpecification); } @Override public ErlangRecordDefinition getRecord(String name) { return myRecordsMap.getValue().get(name); } @Nullable public ErlangImportFunction getImportedFunction(String name, final int arity) { MultiMap<String, ErlangImportFunction> importsMap = myImportsMap.getValue(); Collection<ErlangImportFunction> importFunctions = importsMap.get(name); return ContainerUtil.find(importFunctions, importFunction -> arity == ErlangPsiImplUtil.getArity(importFunction)); } @NotNull @Override public List<ErlangImportFunction> getImportedFunctions() { return myImportValue.getValue(); } private List<ErlangImportFunction> calcImports() { ArrayList<ErlangImportFunction> result = new ArrayList<>(); for (ErlangAttribute attribute : getAttributes()) { ErlangImportDirective importDirective = attribute.getImportDirective(); ErlangImportFunctions importFunctions = importDirective != null ? importDirective.getImportFunctions() : null; List<ErlangImportFunction> functions = importFunctions != null ? importFunctions.getImportFunctionList() : null; ContainerUtil.addAll(result, ContainerUtil.notNullize(functions)); } return result; } private List<ErlangFunction> calcFunctions() { return calcChildren(ErlangFunction.class, ErlangTypes.ERL_FUNCTION, ErlangFunctionStubElementType.ARRAY_FACTORY); } private List<ErlangAttribute> calcAttributes() { return collectChildrenDummyAware(ErlangAttribute.class); } private List<ErlangRule> calcRules() { return collectChildrenDummyAware(ErlangRule.class); } @Override public void addDeclaredParseTransforms(@NotNull Set<String> parseTransforms) { ErlangFileStub stub = getStub(); if (stub != null) { String fromStub = stub.getParseTransforms(); List<String> split = fromStub != null ? StringUtil.split(fromStub, ",") : ContainerUtil.<String>emptyList(); parseTransforms.addAll(split); return; } for (ErlangAttribute attribute : getAttributes()) { ErlangAtomAttribute atomAttribute = attribute.getAtomAttribute(); String attributeName = null != atomAttribute ? atomAttribute.getName() : null; ErlangAttrVal attrVal = atomAttribute != null ? atomAttribute.getAttrVal() : null; if (!"compile".equals(attributeName) || attrVal == null) continue; for (ErlangExpression expression : attrVal.getExpressionList()) { //TODO support macros if (expression instanceof ErlangListExpression) { ErlangPsiImplUtil.extractParseTransforms((ErlangListExpression) expression, parseTransforms); } if (expression instanceof ErlangTupleExpression) { ErlangPsiImplUtil.extractParseTransforms((ErlangTupleExpression) expression, parseTransforms); } } } } @Nullable @Override public PsiElement getNameIdentifier() { return null; // hack for inplace rename: InplaceRefactoring#getVariable() } private <T extends StubBasedPsiElement<?>> List<T> calcChildren(@NotNull Class<T> clazz, @NotNull IElementType elementType, @NotNull ArrayFactory<T> arrayFactory) { ErlangFileStub stub = getStub(); return stub != null ? getChildrenByType(stub, elementType, arrayFactory) : collectChildrenDummyAware(clazz); } @NotNull private <T> CachedValue<T> createCachedValue(@NotNull CachedValueProvider<T> provider) { return CachedValuesManager.getManager(getProject()).createCachedValue(provider, false); } @NotNull private <T extends PsiElement> List<T> collectChildrenDummyAware(@NotNull final Class<T> clazz) { final List<T> result = ContainerUtil.newArrayList(); processChildrenDummyAware(this, element -> { if (clazz.isInstance(element)) { //noinspection unchecked result.add((T)element); } return true; }); return result; } private static boolean processChildrenDummyAware(ErlangFileImpl file, final Processor<PsiElement> processor) { return new Processor<PsiElement>() { @Override public boolean process(PsiElement psiElement) { for (PsiElement child = psiElement.getFirstChild(); child != null; child = child.getNextSibling()) { if (child instanceof ErlangParserUtil.DummyBlock) { if (!process(child)) return false; } else if (!processor.process(child)) return false; } return true; } }.process(file); } @NotNull private static <E extends StubBasedPsiElement<?>> List<E> getChildrenByType(@NotNull ErlangFileStub stub, @NotNull IElementType elementType, @NotNull ArrayFactory<E> arrayFactory) { return ContainerUtil.list(stub.getChildrenByType(elementType, arrayFactory)); } private abstract class ValueProvider<T> implements CachedValueProvider<T> { @NotNull @Override public final Result<T> compute() { return Result.create(computeValue(), ErlangFileImpl.this); } @Nullable protected abstract T computeValue(); } }