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