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