001 /*
002 * Copyright 2010-2015 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.kotlin.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.jvm.functions.Function0;
026 import org.jetbrains.annotations.NotNull;
027 import org.jetbrains.annotations.Nullable;
028 import org.jetbrains.kotlin.analyzer.AnalysisResult;
029 import org.jetbrains.kotlin.descriptors.ClassDescriptor;
030 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
031 import org.jetbrains.kotlin.diagnostics.*;
032 import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages;
033 import org.jetbrains.kotlin.load.java.JavaBindingContext;
034 import org.jetbrains.kotlin.load.java.JvmAbi;
035 import org.jetbrains.kotlin.load.java.components.TraceBasedErrorReporter;
036 import org.jetbrains.kotlin.load.java.components.TraceBasedErrorReporter.AbiVersionErrorData;
037 import org.jetbrains.kotlin.psi.KtFile;
038 import org.jetbrains.kotlin.resolve.AnalyzingUtils;
039 import org.jetbrains.kotlin.resolve.BindingContext;
040 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
041 import org.jetbrains.kotlin.resolve.DescriptorUtils;
042 import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
043 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
044 import org.jetbrains.kotlin.utils.StringsKt;
045
046 import java.util.ArrayList;
047 import java.util.Collection;
048 import java.util.List;
049
050 import static com.intellij.openapi.util.io.FileUtil.toSystemDependentName;
051 import static org.jetbrains.kotlin.diagnostics.DiagnosticUtils.sortedDiagnostics;
052
053 public final class AnalyzerWithCompilerReport {
054
055 @NotNull
056 public static CompilerMessageSeverity convertSeverity(@NotNull Severity severity) {
057 switch (severity) {
058 case INFO:
059 return CompilerMessageSeverity.INFO;
060 case ERROR:
061 return CompilerMessageSeverity.ERROR;
062 case WARNING:
063 return CompilerMessageSeverity.WARNING;
064 }
065 throw new IllegalStateException("Unknown severity: " + severity);
066 }
067
068 private static final DiagnosticFactory0<PsiErrorElement> SYNTAX_ERROR_FACTORY = DiagnosticFactory0.create(Severity.ERROR);
069
070 private final MessageSeverityCollector messageCollector;
071 private AnalysisResult analysisResult = null;
072
073 public AnalyzerWithCompilerReport(@NotNull MessageCollector collector) {
074 messageCollector = new MessageSeverityCollector(collector);
075 }
076
077 private static boolean reportDiagnostic(
078 @NotNull Diagnostic diagnostic,
079 @NotNull DiagnosticMessageReporter reporter,
080 boolean incompatibleFilesFound
081 ) {
082 if (!diagnostic.isValid()) return false;
083
084 String render;
085 if (diagnostic instanceof MyDiagnostic) {
086 render = ((MyDiagnostic) diagnostic).message;
087 }
088 else {
089 render = DefaultErrorMessages.render(diagnostic);
090 }
091
092 if (incompatibleFilesFound && Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS.contains(diagnostic.getFactory())) {
093 render += "\n(note: this may be caused by the fact that some classes compiled with an incompatible version of Kotlin " +
094 "were found in the classpath. Such classes cannot be loaded properly by this version of Kotlin compiler. " +
095 "See below for more information)";
096 }
097
098 PsiFile file = diagnostic.getPsiFile();
099 reporter.report(diagnostic, file, render);
100
101 return diagnostic.getSeverity() == Severity.ERROR;
102 }
103
104 private void reportIncompleteHierarchies() {
105 assert analysisResult != null;
106 BindingContext bindingContext = analysisResult.getBindingContext();
107 Collection<ClassDescriptor> classes = bindingContext.getKeys(TraceBasedErrorReporter.INCOMPLETE_HIERARCHY);
108 if (!classes.isEmpty()) {
109 StringBuilder message = new StringBuilder("Supertypes of the following classes cannot be resolved. " +
110 "Please make sure you have the required dependencies in the classpath:\n");
111 for (ClassDescriptor descriptor : classes) {
112 String fqName = DescriptorUtils.getFqName(descriptor).asString();
113 List<String> unresolved = bindingContext.get(TraceBasedErrorReporter.INCOMPLETE_HIERARCHY, descriptor);
114 assert unresolved != null && !unresolved.isEmpty() :
115 "Incomplete hierarchy should be reported with names of unresolved superclasses: " + fqName;
116 message.append(" class ").append(fqName)
117 .append(", unresolved supertypes: ").append(StringsKt.join(unresolved, ", "))
118 .append("\n");
119 }
120 messageCollector.report(CompilerMessageSeverity.ERROR, message.toString(), CompilerMessageLocation.NO_LOCATION);
121 }
122 }
123
124 private void reportAlternativeSignatureErrors() {
125 assert analysisResult != null;
126 BindingContext bc = analysisResult.getBindingContext();
127 Collection<DeclarationDescriptor> descriptorsWithErrors = bc.getKeys(JavaBindingContext.LOAD_FROM_JAVA_SIGNATURE_ERRORS);
128 if (!descriptorsWithErrors.isEmpty()) {
129 StringBuilder message = new StringBuilder("The following Java entities have annotations with wrong Kotlin signatures:\n");
130 for (DeclarationDescriptor descriptor : descriptorsWithErrors) {
131 PsiElement declaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor);
132 assert declaration instanceof PsiModifierListOwner;
133
134 List<String> errors = bc.get(JavaBindingContext.LOAD_FROM_JAVA_SIGNATURE_ERRORS, descriptor);
135 assert errors != null && !errors.isEmpty();
136
137 String externalName = PsiFormatUtil.getExternalName((PsiModifierListOwner) declaration);
138 message.append(externalName).append(":\n");
139
140 for (String error : errors) {
141 message.append(" ").append(error).append("\n");
142 }
143 }
144 messageCollector.report(CompilerMessageSeverity.ERROR, message.toString(), CompilerMessageLocation.NO_LOCATION);
145 }
146 }
147
148 @NotNull
149 private List<AbiVersionErrorData> getAbiVersionErrors() {
150 assert analysisResult != null;
151 BindingContext bindingContext = analysisResult.getBindingContext();
152
153 Collection<String> errorClasses = bindingContext.getKeys(TraceBasedErrorReporter.ABI_VERSION_ERRORS);
154 List<AbiVersionErrorData> result = new ArrayList<AbiVersionErrorData>(errorClasses.size());
155 for (String kotlinClass : errorClasses) {
156 result.add(bindingContext.get(TraceBasedErrorReporter.ABI_VERSION_ERRORS, kotlinClass));
157 }
158
159 return result;
160 }
161
162 private void reportAbiVersionErrors(@NotNull List<AbiVersionErrorData> errors) {
163 for (AbiVersionErrorData data : errors) {
164 String path = toSystemDependentName(data.getFilePath());
165 messageCollector.report(
166 CompilerMessageSeverity.ERROR,
167 "Class '" + JvmClassName.byClassId(data.getClassId()) + "' was compiled with an incompatible version of Kotlin. " +
168 "Its ABI version is " + data.getActualVersion() + ", expected ABI version is " + JvmAbi.VERSION,
169 CompilerMessageLocation.create(path, -1, -1, null)
170 );
171 }
172 }
173
174 public static boolean reportDiagnostics(
175 @NotNull Diagnostics diagnostics,
176 @NotNull DiagnosticMessageReporter reporter,
177 boolean incompatibleFilesFound
178 ) {
179 boolean hasErrors = false;
180 for (Diagnostic diagnostic : sortedDiagnostics(diagnostics.all())) {
181 hasErrors |= reportDiagnostic(diagnostic, reporter, incompatibleFilesFound);
182 }
183 return hasErrors;
184 }
185
186 public static boolean reportDiagnostics(
187 @NotNull Diagnostics diagnostics,
188 @NotNull MessageCollector messageCollector,
189 boolean incompatibleFilesFound
190 ) {
191 return reportDiagnostics(diagnostics, new DefaultDiagnosticReporter(messageCollector), incompatibleFilesFound);
192 }
193
194 public static boolean reportDiagnostics(@NotNull Diagnostics diagnostics, @NotNull MessageCollector messageCollector) {
195 return reportDiagnostics(diagnostics, new DefaultDiagnosticReporter(messageCollector), false);
196 }
197
198 private void reportSyntaxErrors(@NotNull Collection<KtFile> files) {
199 for (KtFile file : files) {
200 reportSyntaxErrors(file, messageCollector);
201 }
202 }
203
204 public static class SyntaxErrorReport {
205 private final boolean hasErrors;
206 private final boolean allErrorsAtEof;
207
208 public SyntaxErrorReport(boolean hasErrors, boolean allErrorsAtEof) {
209 this.hasErrors = hasErrors;
210 this.allErrorsAtEof = allErrorsAtEof;
211 }
212
213 public boolean isHasErrors() {
214 return hasErrors;
215 }
216
217 public boolean isAllErrorsAtEof() {
218 return allErrorsAtEof;
219 }
220 }
221
222 public static SyntaxErrorReport reportSyntaxErrors(
223 @NotNull final PsiElement file,
224 @NotNull final DiagnosticMessageReporter reporter
225 ) {
226 class ErrorReportingVisitor extends AnalyzingUtils.PsiErrorElementVisitor {
227 boolean hasErrors = false;
228 boolean allErrorsAtEof = true;
229
230 private <E extends PsiElement> void reportDiagnostic(E element, DiagnosticFactory0<E> factory, String message) {
231 MyDiagnostic<?> diagnostic = new MyDiagnostic<E>(element, factory, message);
232 AnalyzerWithCompilerReport.reportDiagnostic(diagnostic, reporter, false);
233 if (element.getTextRange().getStartOffset() != file.getTextRange().getEndOffset()) {
234 allErrorsAtEof = false;
235 }
236 hasErrors = true;
237 }
238
239 @Override
240 public void visitErrorElement(PsiErrorElement element) {
241 String description = element.getErrorDescription();
242 reportDiagnostic(element, SYNTAX_ERROR_FACTORY, StringUtil.isEmpty(description) ? "Syntax error" : description);
243 }
244 }
245 ErrorReportingVisitor visitor = new ErrorReportingVisitor();
246
247 file.accept(visitor);
248
249 return new SyntaxErrorReport(visitor.hasErrors, visitor.allErrorsAtEof);
250 }
251
252 public static SyntaxErrorReport reportSyntaxErrors(@NotNull PsiElement file, @NotNull MessageCollector messageCollector) {
253 return reportSyntaxErrors(file, new DefaultDiagnosticReporter(messageCollector));
254 }
255
256 @Nullable
257 public AnalysisResult getAnalysisResult() {
258 return analysisResult;
259 }
260
261 public boolean hasErrors() {
262 return messageCollector.anyReported(CompilerMessageSeverity.ERROR);
263 }
264
265 public void analyzeAndReport(@NotNull Collection<KtFile> files, @NotNull Function0<AnalysisResult> analyzer) {
266 analysisResult = analyzer.invoke();
267 reportSyntaxErrors(files);
268 List<AbiVersionErrorData> abiVersionErrors = getAbiVersionErrors();
269 reportDiagnostics(analysisResult.getBindingContext().getDiagnostics(), messageCollector, !abiVersionErrors.isEmpty());
270 if (hasErrors()) {
271 reportAbiVersionErrors(abiVersionErrors);
272 }
273 reportIncompleteHierarchies();
274 reportAlternativeSignatureErrors();
275 }
276
277 private static class MyDiagnostic<E extends PsiElement> extends SimpleDiagnostic<E> {
278 private final String message;
279
280 public MyDiagnostic(@NotNull E psiElement, @NotNull DiagnosticFactory0<E> factory, String message) {
281 super(psiElement, factory, Severity.ERROR);
282 this.message = message;
283 }
284
285 @Override
286 public boolean isValid() {
287 return true;
288 }
289 }
290 }