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