package com.jetbrains.lang.dart.util;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.util.*;
import com.intellij.util.PathUtil;
import com.jetbrains.lang.dart.DartTokenTypes;
import com.jetbrains.lang.dart.psi.*;
import com.jetbrains.lang.dart.resolve.DartResolveProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class DartPsiImplUtil {
private static final String TRIPLE_APOS = "'''";
private static final String TRIPLE_QUOTE = "\"\"\"";
private static final String APOS = "'";
private static final String QUOTE = "\"";
private static final String R_TRIPLE_APOS = "r'''";
private static final String R_TRIPLE_QUOTE = "r\"\"\"";
private static final String R_APOS = "r'";
private static final String R_QUOTE = "r\"";
private static final Key<CachedValue<PsiElement>> DART_TYPE_CACHED_RESOLVE_RESULT_KEY = Key.create("DART_TYPE_CACHED_RESOLVE_RESULT_KEY");
@NotNull
public static String getUriString(@NotNull final DartUriBasedDirective uriBasedDirective) {
return getUnquotedDartStringAndItsRange(uriBasedDirective.getUriElement().getText()).first;
}
@NotNull
public static Pair<String, TextRange> getUriStringAndItsRange(@NotNull final DartUriElement uriElement) {
return getUnquotedDartStringAndItsRange(uriElement.getText());
}
@NotNull
public static Pair<String, TextRange> getUnquotedDartStringAndItsRange(@NotNull final String quotedDartString) {
// r'''dart:core'''
// """package:angular/angular.dart"""
// "../foo/bar.dart"
// also can be not closed string when completing for example import '<caret>
final int startOffset;
final int endOffset;
if (quotedDartString.startsWith(TRIPLE_APOS)) {
startOffset = TRIPLE_APOS.length();
endOffset = quotedDartString.endsWith(TRIPLE_APOS) && quotedDartString.length() >= TRIPLE_APOS.length() * 2
? quotedDartString.length() - TRIPLE_APOS.length() : quotedDartString.length();
}
else if (quotedDartString.startsWith(TRIPLE_QUOTE)) {
startOffset = TRIPLE_QUOTE.length();
endOffset = quotedDartString.endsWith(TRIPLE_QUOTE) && quotedDartString.length() >= TRIPLE_QUOTE.length() * 2
? quotedDartString.length() - TRIPLE_QUOTE.length() : quotedDartString.length();
}
else if (quotedDartString.startsWith(APOS)) {
startOffset = APOS.length();
endOffset = quotedDartString.endsWith(APOS) && quotedDartString.length() >= APOS.length() * 2
? quotedDartString.length() - APOS.length() : quotedDartString.length();
}
else if (quotedDartString.startsWith(QUOTE)) {
startOffset = QUOTE.length();
endOffset = quotedDartString.endsWith(QUOTE) && quotedDartString.length() >= QUOTE.length() * 2
? quotedDartString.length() - QUOTE.length() : quotedDartString.length();
}
else if (quotedDartString.startsWith(R_TRIPLE_APOS)) {
startOffset = R_TRIPLE_APOS.length();
endOffset = quotedDartString.endsWith(TRIPLE_APOS) && quotedDartString.length() >= R_TRIPLE_APOS.length() + TRIPLE_APOS.length()
? quotedDartString.length() - TRIPLE_APOS.length() : quotedDartString.length();
}
else if (quotedDartString.startsWith(R_TRIPLE_QUOTE)) {
startOffset = R_TRIPLE_QUOTE.length();
endOffset = quotedDartString.endsWith(TRIPLE_QUOTE) && quotedDartString.length() >= R_TRIPLE_QUOTE.length() + TRIPLE_QUOTE.length()
? quotedDartString.length() - TRIPLE_QUOTE.length() : quotedDartString.length();
}
else if (quotedDartString.startsWith(R_APOS)) {
startOffset = R_APOS.length();
endOffset = quotedDartString.endsWith(APOS) && quotedDartString.length() >= R_APOS.length() + APOS.length()
? quotedDartString.length() - APOS.length() : quotedDartString.length();
}
else if (quotedDartString.startsWith(R_QUOTE)) {
startOffset = R_QUOTE.length();
endOffset = quotedDartString.endsWith(QUOTE) && quotedDartString.length() >= R_QUOTE.length() + QUOTE.length()
? quotedDartString.length() - QUOTE.length() : quotedDartString.length();
}
else {
startOffset = 0;
endOffset = quotedDartString.length();
}
return Pair.create(quotedDartString.substring(startOffset, endOffset), TextRange.create(startOffset, endOffset));
}
@NotNull
public static String getLibraryName(@NotNull final DartPartOfStatement partOfStatement) {
final DartLibraryId libraryId = partOfStatement.getLibraryId();
if (libraryId != null) {
return libraryId.getText();
}
final DartUriElement uriElement = partOfStatement.getUriElement();
assert uriElement != null : "[" + partOfStatement.toString() + "]";
final String uri = uriElement.getUriStringAndItsRange().first;
final VirtualFile file = DartResolveUtil.getRealVirtualFile(partOfStatement.getContainingFile());
final VirtualFile targetFile = file == null ? null : DartResolveUtil.getImportedFile(partOfStatement.getProject(), file, uri);
final PsiFile targetPsiFile = targetFile == null || file.equals(targetFile) ? null : partOfStatement.getManager().findFile(targetFile);
final DartLibraryStatement libraryStatement = targetPsiFile == null
? null
: PsiTreeUtil.getChildOfType(targetPsiFile, DartLibraryStatement.class);
if (libraryStatement != null) {
return libraryStatement.getLibraryNameElement().getName();
}
return PathUtil.getFileName(uri);
}
@NotNull
public static List<DartMetadata> getMetadataList(@NotNull DartLabel element) {
return Collections.emptyList();
}
@NotNull
public static List<DartMetadata> getMetadataList(@NotNull DartEnumConstantDeclaration element) {
return Collections.emptyList();
}
@NotNull
public static List<DartMetadata> getMetadataList(@NotNull DartVarDeclarationListPart element) {
return Collections.emptyList();
}
@Nullable
public static DartComponentName getComponentName(@NotNull DartNamedConstructorDeclaration element) {
final List<DartComponentName> list = element.getComponentNameList();
return list.size() == 2 ? list.get(1) : null; // todo somehow remove this bogus code, it is here just to mimic old behavior
}
@Nullable
public static DartComponentName getComponentName(@NotNull DartFactoryConstructorDeclaration element) {
final List<DartComponentName> list = element.getComponentNameList();
// todo somehow remove this bogus code, it is here just to mimic old behavior
return list.size() == 2 ? list.get(1) : list.size() == 1 ? list.get(0) : null;
}
@Nullable
public static DartReferenceExpression getReferenceExpression(@NotNull final DartType dartType) {
final DartSimpleType simpleType = dartType.getSimpleType();
return simpleType == null ? null : simpleType.getReferenceExpression();
}
@Nullable
public static DartTypeArguments getTypeArguments(@NotNull final DartType dartType) {
final DartSimpleType simpleType = dartType.getSimpleType();
return simpleType == null ? null : simpleType.getTypeArguments();
}
@Nullable
public static PsiElement resolveReference(@NotNull final DartType dartType) {
CachedValue<PsiElement> cachedValue = dartType.getUserData(DART_TYPE_CACHED_RESOLVE_RESULT_KEY);
if (cachedValue == null) {
cachedValue = CachedValuesManager.getManager(dartType.getProject()).createCachedValue(
() -> new CachedValueProvider.Result<>(doResolveTypeReference(dartType), PsiModificationTracker.MODIFICATION_COUNT), false);
dartType.putUserData(DART_TYPE_CACHED_RESOLVE_RESULT_KEY, cachedValue);
}
return cachedValue.getValue();
}
private static PsiElement doResolveTypeReference(final DartType dartType) {
final DartExpression expression = dartType.getReferenceExpression();
if (expression == null) {
return null;
}
final String typeName = expression.getText();
if (typeName.indexOf('.') != -1) {
return ((DartReference)expression).resolve();
}
List<DartComponentName> result = new ArrayList<>();
final DartResolveProcessor dartResolveProcessor = new DartResolveProcessor(result, typeName);
final VirtualFile virtualFile = DartResolveUtil.getRealVirtualFile(dartType.getContainingFile());
if (virtualFile != null) {
DartResolveUtil.processTopLevelDeclarations(dartType, dartResolveProcessor, virtualFile, typeName);
}
// find type parameter
if (result.isEmpty()) {
PsiTreeUtil.treeWalkUp(dartResolveProcessor, dartType, null, ResolveState.initial());
for (Iterator<DartComponentName> iterator = result.iterator(); iterator.hasNext(); ) {
if (!(iterator.next().getParent() instanceof DartTypeParameter)) {
iterator.remove();
}
}
}
// global
if (result.isEmpty()) {
final List<VirtualFile> libraryFiles = DartResolveUtil.findLibrary(dartType.getContainingFile());
DartResolveUtil.processTopLevelDeclarations(dartType, dartResolveProcessor, libraryFiles, typeName);
}
return result.isEmpty() ? null : result.iterator().next();
}
@Nullable
public static DartComponentName findComponentName(final @NotNull DartNormalFormalParameter normalFormalParameter) {
final DartFunctionFormalParameter functionFormalParameter = normalFormalParameter.getFunctionFormalParameter();
final DartFieldFormalParameter fieldFormalParameter = normalFormalParameter.getFieldFormalParameter();
final DartSimpleFormalParameter simpleFormalParameter = normalFormalParameter.getSimpleFormalParameter();
if (functionFormalParameter != null) return functionFormalParameter.getComponentName();
if (fieldFormalParameter != null) return null;
if (simpleFormalParameter != null) return simpleFormalParameter.getComponentName();
return null;
}
public static DartExpression getParameterReferenceExpression(DartNamedArgument argument) {
return PsiTreeUtil.getChildOfType(argument, DartExpression.class);
}
public static DartExpression getExpression(DartNamedArgument argument) {
final DartExpression[] expressions = PsiTreeUtil.getChildrenOfType(argument, DartExpression.class);
return expressions != null && expressions.length > 1 ? expressions[expressions.length - 1] : null;
}
@Nullable
public static IDartBlock getBlock(DartFunctionBody functionBody) {
return PsiTreeUtil.getChildOfType(functionBody, IDartBlock.class);
}
public static boolean isConstantObjectExpression(@NotNull final DartNewExpression newExpression) {
final PsiElement child = newExpression.getFirstChild();
return child != null && child.getNode().getElementType() == DartTokenTypes.CONST;
}
@Nullable
public static DartArguments getArguments(@NotNull final DartNewExpression newExpression) {
return PsiTreeUtil.findChildOfType(newExpression, DartArguments.class);
}
@Nullable
public static DartArguments getArguments(@NotNull final DartCallExpression callExpression) {
return PsiTreeUtil.findChildOfType(callExpression, DartArguments.class);
}
@Nullable
public static DartExpression getCondition(@NotNull DartPsiCompositeElement ifStatement) {
return PsiTreeUtil.findChildOfType(ifStatement, DartExpression.class);
}
@Nullable
public static PsiElement getThenBranch(@NotNull DartIfStatement ifStatement) {
return getBranchAfter(getCondition(ifStatement));
}
@Nullable
public static PsiElement getElseBranch(@NotNull DartIfStatement ifStatement) {
return getBranchAfter(getThenBranch(ifStatement));
}
@Nullable
private static PsiElement getBranchAfter(@Nullable PsiElement child) {
return PsiTreeUtil.skipSiblingsForward(child, LeafPsiElement.class, PsiWhiteSpace.class, PsiComment.class);
}
@Nullable
public static PsiElement getDoBody(@NotNull DartDoWhileStatement doStatement) {
return getBranchAfter(doStatement);
}
@Nullable
public static PsiElement getForBody(@NotNull DartForStatement forStatement) {
return forStatement.getLastChild();
}
@Nullable
public static PsiElement getWhileBody(@NotNull DartWhileStatement whileStatement) {
return getBranchAfter(getCondition(whileStatement));
}
}