001/* 002 * Copyright 2010-2013 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.jetbrains.jet.lang.diagnostics; 018 019import com.google.common.collect.Lists; 020import com.intellij.lang.ASTNode; 021import com.intellij.openapi.application.ApplicationManager; 022import com.intellij.openapi.editor.Document; 023import com.intellij.openapi.util.TextRange; 024import com.intellij.openapi.vfs.VirtualFile; 025import com.intellij.psi.PsiElement; 026import com.intellij.psi.PsiFile; 027import org.jetbrains.annotations.NotNull; 028import org.jetbrains.annotations.Nullable; 029import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; 030import org.jetbrains.jet.lang.psi.JetExpression; 031import org.jetbrains.jet.lang.resolve.BindingContext; 032import org.jetbrains.jet.lang.resolve.BindingContextUtils; 033 034import java.util.Collection; 035import java.util.Collections; 036import java.util.Comparator; 037import java.util.List; 038 039public class DiagnosticUtils { 040 @NotNull 041 private static final Comparator<TextRange> TEXT_RANGE_COMPARATOR = new Comparator<TextRange>() { 042 @Override 043 public int compare(TextRange o1, TextRange o2) { 044 if (o1.getStartOffset() != o2.getStartOffset()) { 045 return o1.getStartOffset() - o2.getStartOffset(); 046 } 047 return o1.getEndOffset() - o2.getEndOffset(); 048 } 049 }; 050 051 private DiagnosticUtils() { 052 } 053 054 public static String atLocation(BindingContext bindingContext, DeclarationDescriptor descriptor) { 055 PsiElement element = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor); 056 if (element == null) { 057 element = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor.getOriginal()); 058 } 059 if (element == null && descriptor instanceof ASTNode) { 060 element = DiagnosticUtils.getClosestPsiElement((ASTNode) descriptor); 061 } 062 if (element != null) { 063 return DiagnosticUtils.atLocation(element); 064 } else { 065 return "unknown location"; 066 } 067 } 068 069 public static String atLocation(JetExpression expression) { 070 return atLocation(expression.getNode()); 071 } 072 073 public static String atLocation(@NotNull PsiElement element) { 074 return atLocation(element.getNode()); 075 } 076 077 public static String atLocation(@NotNull ASTNode node) { 078 int startOffset = node.getStartOffset(); 079 PsiElement element = getClosestPsiElement(node); 080 if (element != null) { 081 return atLocation(element.getContainingFile(), element.getTextRange()); 082 } 083 return "' at offset " + startOffset + " (line and file unknown: no PSI element)"; 084 } 085 086 @Nullable 087 public static PsiElement getClosestPsiElement(@NotNull ASTNode node) { 088 while (node.getPsi() == null) { 089 node = node.getTreeParent(); 090 } 091 return node.getPsi(); 092 } 093 094 @NotNull 095 public static PsiFile getContainingFile(@NotNull ASTNode node) { 096 PsiElement closestPsiElement = getClosestPsiElement(node); 097 assert closestPsiElement != null : "This node is not contained by a file"; 098 return closestPsiElement.getContainingFile(); 099 } 100 101 @NotNull 102 public static String atLocation(@NotNull PsiFile file, @NotNull TextRange textRange) { 103 Document document = file.getViewProvider().getDocument(); 104 return atLocation(file, textRange, document); 105 } 106 107 @NotNull 108 public static String atLocation(PsiFile file, TextRange textRange, Document document) { 109 int offset = textRange.getStartOffset(); 110 VirtualFile virtualFile = file.getVirtualFile(); 111 String pathSuffix = " in " + (virtualFile == null ? file.getName() : virtualFile.getPath()); 112 return offsetToLineAndColumn(document, offset).toString() + pathSuffix; 113 } 114 115 @NotNull 116 public static LineAndColumn getLineAndColumn(@NotNull Diagnostic diagnostic) { 117 PsiFile file = diagnostic.getPsiFile(); 118 List<TextRange> textRanges = diagnostic.getTextRanges(); 119 if (textRanges.isEmpty()) return LineAndColumn.NONE; 120 TextRange firstRange = firstRange(textRanges); 121 return getLineAndColumnInPsiFile(file, firstRange); 122 } 123 124 @NotNull 125 public static LineAndColumn getLineAndColumnInPsiFile(PsiFile file, TextRange range) { 126 Document document = file.getViewProvider().getDocument(); 127 return offsetToLineAndColumn(document, range.getStartOffset()); 128 } 129 130 @NotNull 131 public static LineAndColumn offsetToLineAndColumn(Document document, int offset) { 132 if (document == null) { 133 return new LineAndColumn(-1, offset); 134 } 135 136 int lineNumber = document.getLineNumber(offset); 137 int lineStartOffset = document.getLineStartOffset(lineNumber); 138 int column = offset - lineStartOffset; 139 140 return new LineAndColumn(lineNumber + 1, column + 1); 141 } 142 143 public static void throwIfRunningOnServer(Throwable e) { 144 // This is needed for the Web Demo server to log the exceptions coming from the analyzer instead of showing them in the editor. 145 if (System.getProperty("kotlin.running.in.server.mode", "false").equals("true") || ApplicationManager.getApplication().isUnitTestMode()) { 146 if (e instanceof RuntimeException) { 147 throw (RuntimeException) e; 148 } 149 if (e instanceof Error) { 150 throw (Error) e; 151 } 152 throw new RuntimeException(e); 153 } 154 } 155 156 @NotNull 157 private static TextRange firstRange(@NotNull List<TextRange> ranges) { 158 return Collections.min(ranges, TEXT_RANGE_COMPARATOR); 159 } 160 161 @NotNull 162 public static List<Diagnostic> sortedDiagnostics(@NotNull Collection<Diagnostic> diagnostics) { 163 Comparator<Diagnostic> diagnosticComparator = new Comparator<Diagnostic>() { 164 @Override 165 public int compare(Diagnostic d1, Diagnostic d2) { 166 String path1 = d1.getPsiFile().getViewProvider().getVirtualFile().getPath(); 167 String path2 = d2.getPsiFile().getViewProvider().getVirtualFile().getPath(); 168 if (!path1.equals(path2)) return path1.compareTo(path2); 169 170 TextRange range1 = firstRange(d1.getTextRanges()); 171 TextRange range2 = firstRange(d2.getTextRanges()); 172 173 if (!range1.equals(range2)) { 174 return TEXT_RANGE_COMPARATOR.compare(range1, range2); 175 } 176 177 return d1.getFactory().getName().compareTo(d2.getFactory().getName()); 178 } 179 }; 180 List<Diagnostic> result = Lists.newArrayList(diagnostics); 181 Collections.sort(result, diagnosticComparator); 182 return result; 183 } 184 185 public static final class LineAndColumn { 186 187 public static final LineAndColumn NONE = new LineAndColumn(-1, -1); 188 189 private final int line; 190 private final int column; 191 192 public LineAndColumn(int line, int column) { 193 this.line = line; 194 this.column = column; 195 } 196 197 public int getLine() { 198 return line; 199 } 200 201 public int getColumn() { 202 return column; 203 } 204 205 // NOTE: This method is used for presenting positions to the user 206 @Override 207 public String toString() { 208 if (line < 0) { 209 return "(offset: " + column + " line unknown)"; 210 } 211 return "(" + line + "," + column + ")"; 212 } 213 } 214}