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