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.cli.common.messages; 018 019import com.intellij.openapi.util.text.StringUtil; 020import com.intellij.openapi.vfs.VirtualFile; 021import com.intellij.psi.PsiClass; 022import com.intellij.psi.PsiElement; 023import com.intellij.psi.PsiErrorElement; 024import com.intellij.psi.PsiModifierListOwner; 025import com.intellij.psi.util.PsiFormatUtil; 026import jet.Function0; 027import org.jetbrains.annotations.NotNull; 028import org.jetbrains.annotations.Nullable; 029import org.jetbrains.jet.analyzer.AnalyzeExhaust; 030import org.jetbrains.jet.lang.descriptors.ClassDescriptor; 031import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; 032import org.jetbrains.jet.lang.diagnostics.*; 033import org.jetbrains.jet.lang.diagnostics.rendering.DefaultErrorMessages; 034import org.jetbrains.jet.lang.psi.JetFile; 035import org.jetbrains.jet.lang.psi.JetIdeTemplate; 036import org.jetbrains.jet.lang.resolve.AnalyzingUtils; 037import org.jetbrains.jet.lang.resolve.BindingContext; 038import org.jetbrains.jet.lang.resolve.BindingContextUtils; 039import org.jetbrains.jet.lang.resolve.DescriptorUtils; 040import org.jetbrains.jet.lang.resolve.java.AbiVersionUtil; 041import org.jetbrains.jet.lang.resolve.java.JavaBindingContext; 042import org.jetbrains.jet.lang.resolve.java.JvmAbi; 043 044import java.util.Collection; 045import java.util.List; 046 047import static org.jetbrains.jet.lang.diagnostics.DiagnosticUtils.sortedDiagnostics; 048 049public final class AnalyzerWithCompilerReport { 050 051 @NotNull 052 private static CompilerMessageSeverity convertSeverity(@NotNull Severity severity) { 053 switch (severity) { 054 case INFO: 055 return CompilerMessageSeverity.INFO; 056 case ERROR: 057 return CompilerMessageSeverity.ERROR; 058 case WARNING: 059 return CompilerMessageSeverity.WARNING; 060 } 061 throw new IllegalStateException("Unknown severity: " + severity); 062 } 063 064 @NotNull 065 private static final DiagnosticFactory0<PsiErrorElement> SYNTAX_ERROR_FACTORY = DiagnosticFactory0.create(Severity.ERROR); 066 @NotNull 067 private static final DiagnosticFactory0<JetIdeTemplate> UNRESOLVED_IDE_TEMPLATE_ERROR_FACTORY 068 = DiagnosticFactory0.create(Severity.ERROR); 069 070 private boolean hasErrors = false; 071 @NotNull 072 private final MessageCollector messageCollectorWrapper; 073 @Nullable 074 private AnalyzeExhaust analyzeExhaust = null; 075 076 public AnalyzerWithCompilerReport(@NotNull final MessageCollector collector) { 077 messageCollectorWrapper = new MessageCollector() { 078 @Override 079 public void report(@NotNull CompilerMessageSeverity severity, 080 @NotNull String message, 081 @NotNull CompilerMessageLocation location) { 082 if (CompilerMessageSeverity.ERRORS.contains(severity)) { 083 hasErrors = true; 084 } 085 collector.report(severity, message, location); 086 } 087 }; 088 } 089 090 private static boolean reportDiagnostic(@NotNull Diagnostic diagnostic, @NotNull MessageCollector messageCollector) { 091 if (!diagnostic.isValid()) return false; 092 DiagnosticUtils.LineAndColumn lineAndColumn = DiagnosticUtils.getLineAndColumn(diagnostic); 093 VirtualFile virtualFile = diagnostic.getPsiFile().getVirtualFile(); 094 String path = virtualFile == null ? null : virtualFile.getPath(); 095 String render; 096 if (diagnostic instanceof MyDiagnostic) { 097 render = ((MyDiagnostic)diagnostic).message; 098 } 099 else { 100 render = DefaultErrorMessages.RENDERER.render(diagnostic); 101 } 102 messageCollector.report(convertSeverity(diagnostic.getSeverity()), render, 103 CompilerMessageLocation.create(path, lineAndColumn.getLine(), lineAndColumn.getColumn())); 104 return diagnostic.getSeverity() == Severity.ERROR; 105 } 106 107 private void reportIncompleteHierarchies() { 108 assert analyzeExhaust != null; 109 Collection<ClassDescriptor> incompletes = analyzeExhaust.getBindingContext().getKeys(BindingContext.INCOMPLETE_HIERARCHY); 110 if (!incompletes.isEmpty()) { 111 StringBuilder message = new StringBuilder("The following classes have incomplete hierarchies:\n"); 112 for (ClassDescriptor incomplete : incompletes) { 113 String fqName = DescriptorUtils.getFQName(incomplete).asString(); 114 message.append(" ").append(fqName).append("\n"); 115 } 116 messageCollectorWrapper.report(CompilerMessageSeverity.ERROR, message.toString(), CompilerMessageLocation.NO_LOCATION); 117 } 118 } 119 120 private void reportAlternativeSignatureErrors() { 121 assert analyzeExhaust != null; 122 BindingContext bc = analyzeExhaust.getBindingContext(); 123 Collection<DeclarationDescriptor> descriptorsWithErrors = bc.getKeys(JavaBindingContext.LOAD_FROM_JAVA_SIGNATURE_ERRORS); 124 if (!descriptorsWithErrors.isEmpty()) { 125 StringBuilder message = new StringBuilder("The following Java entities have annotations with wrong Kotlin signatures:\n"); 126 for (DeclarationDescriptor descriptor : descriptorsWithErrors) { 127 PsiElement declaration = BindingContextUtils.descriptorToDeclaration(bc, descriptor); 128 assert declaration instanceof PsiModifierListOwner; 129 130 List<String> errors = bc.get(JavaBindingContext.LOAD_FROM_JAVA_SIGNATURE_ERRORS, descriptor); 131 assert errors != null && !errors.isEmpty(); 132 133 String externalName = PsiFormatUtil.getExternalName((PsiModifierListOwner) declaration); 134 message.append(externalName).append(":\n"); 135 136 for (String error : errors) { 137 message.append(" ").append(error).append("\n"); 138 } 139 } 140 messageCollectorWrapper.report(CompilerMessageSeverity.ERROR, 141 message.toString(), CompilerMessageLocation.NO_LOCATION); 142 } 143 } 144 145 private void reportAbiVersionErrors() { 146 assert analyzeExhaust != null; 147 BindingContext bindingContext = analyzeExhaust.getBindingContext(); 148 149 Collection<PsiClass> psiClasses = bindingContext.getKeys(AbiVersionUtil.ABI_VERSION_ERRORS); 150 for (PsiClass psiClass : psiClasses) { 151 Integer abiVersion = bindingContext.get(AbiVersionUtil.ABI_VERSION_ERRORS, psiClass); 152 messageCollectorWrapper.report(CompilerMessageSeverity.ERROR, 153 "Class '" + psiClass.getQualifiedName() + "' was compiled with an incompatible version of Kotlin. " + 154 "Its ABI version is " + abiVersion + ", expected ABI version is " + JvmAbi.VERSION, 155 MessageUtil.psiElementToMessageLocation(psiClass)); 156 } 157 } 158 159 public static boolean reportDiagnostics(@NotNull BindingContext bindingContext, @NotNull MessageCollector messageCollector) { 160 boolean hasErrors = false; 161 for (Diagnostic diagnostic : sortedDiagnostics(bindingContext.getDiagnostics())) { 162 hasErrors |= reportDiagnostic(diagnostic, messageCollector); 163 } 164 return hasErrors; 165 } 166 167 private void reportSyntaxErrors(@NotNull Collection<JetFile> files) { 168 for (JetFile file : files) { 169 reportSyntaxErrors(file, messageCollectorWrapper); 170 } 171 } 172 173 public static class SyntaxErrorReport { 174 private final boolean hasErrors; 175 private final boolean onlyErrorAtEof; 176 177 public SyntaxErrorReport(boolean hasErrors, boolean onlyErrorAtEof) { 178 this.hasErrors = hasErrors; 179 this.onlyErrorAtEof = onlyErrorAtEof; 180 } 181 182 public boolean isHasErrors() { 183 return hasErrors; 184 } 185 186 public boolean isOnlyErrorAtEof() { 187 return onlyErrorAtEof; 188 } 189 } 190 191 public static SyntaxErrorReport reportSyntaxErrors(@NotNull final PsiElement file, @NotNull final MessageCollector messageCollector) { 192 class ErrorReportingVisitor extends AnalyzingUtils.PsiErrorElementVisitor { 193 boolean hasErrors = false; 194 boolean onlyErrorAtEof = false; 195 196 private <E extends PsiElement> void reportDiagnostic(E element, DiagnosticFactory0<E> factory, String message) { 197 MyDiagnostic<?> diagnostic = new MyDiagnostic<E>(element, factory, message); 198 AnalyzerWithCompilerReport.reportDiagnostic(diagnostic, messageCollector); 199 if (element.getTextRange().getStartOffset() == file.getTextRange().getEndOffset()) { 200 onlyErrorAtEof = !hasErrors; 201 } 202 hasErrors = true; 203 } 204 205 @Override 206 public void visitIdeTemplate(JetIdeTemplate expression) { 207 String placeholderText = expression.getPlaceholderText(); 208 reportDiagnostic(expression, UNRESOLVED_IDE_TEMPLATE_ERROR_FACTORY, 209 "Unresolved IDE template" + (StringUtil.isEmpty(placeholderText) ? "" : ": " + placeholderText)); 210 } 211 212 @Override 213 public void visitErrorElement(PsiErrorElement element) { 214 String description = element.getErrorDescription(); 215 reportDiagnostic(element, SYNTAX_ERROR_FACTORY, StringUtil.isEmpty(description) ? "Syntax error" : description); 216 } 217 } 218 ErrorReportingVisitor visitor = new ErrorReportingVisitor(); 219 220 file.accept(visitor); 221 222 return new SyntaxErrorReport(visitor.hasErrors, visitor.onlyErrorAtEof); 223 } 224 225 @Nullable 226 public AnalyzeExhaust getAnalyzeExhaust() { 227 return analyzeExhaust; 228 } 229 230 public boolean hasErrors() { 231 return hasErrors; 232 } 233 234 public void analyzeAndReport(@NotNull Function0<AnalyzeExhaust> analyzer, @NotNull Collection<JetFile> files) { 235 reportSyntaxErrors(files); 236 analyzeExhaust = analyzer.invoke(); 237 reportDiagnostics(analyzeExhaust.getBindingContext(), messageCollectorWrapper); 238 reportIncompleteHierarchies(); 239 reportAlternativeSignatureErrors(); 240 reportAbiVersionErrors(); 241 } 242 243 244 private static class MyDiagnostic<E extends PsiElement> extends SimpleDiagnostic<E> { 245 private String message; 246 247 public MyDiagnostic(@NotNull E psiElement, @NotNull DiagnosticFactory0<E> factory, String message) { 248 super(psiElement, factory, Severity.ERROR); 249 this.message = message; 250 } 251 252 @Override 253 public boolean isValid() { 254 return true; 255 } 256 } 257}