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