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