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