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