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