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