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