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