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