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
017package org.jetbrains.jet.cli.common.messages;
018
019import com.intellij.openapi.util.text.StringUtil;
020import com.intellij.openapi.vfs.VirtualFile;
021import com.intellij.psi.PsiClass;
022import com.intellij.psi.PsiElement;
023import com.intellij.psi.PsiErrorElement;
024import com.intellij.psi.PsiModifierListOwner;
025import com.intellij.psi.util.PsiFormatUtil;
026import jet.Function0;
027import org.jetbrains.annotations.NotNull;
028import org.jetbrains.annotations.Nullable;
029import org.jetbrains.jet.analyzer.AnalyzeExhaust;
030import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
031import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
032import org.jetbrains.jet.lang.diagnostics.*;
033import org.jetbrains.jet.lang.diagnostics.rendering.DefaultErrorMessages;
034import org.jetbrains.jet.lang.psi.JetFile;
035import org.jetbrains.jet.lang.psi.JetIdeTemplate;
036import org.jetbrains.jet.lang.resolve.AnalyzingUtils;
037import org.jetbrains.jet.lang.resolve.BindingContext;
038import org.jetbrains.jet.lang.resolve.BindingContextUtils;
039import org.jetbrains.jet.lang.resolve.DescriptorUtils;
040import org.jetbrains.jet.lang.resolve.java.AbiVersionUtil;
041import org.jetbrains.jet.lang.resolve.java.JavaBindingContext;
042import org.jetbrains.jet.lang.resolve.java.JvmAbi;
043
044import java.util.Collection;
045import java.util.List;
046
047import static org.jetbrains.jet.lang.diagnostics.DiagnosticUtils.sortedDiagnostics;
048
049public 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}