c.s.tools.ide.analysis.modernize/src/com/sun/tools/ide/analysis/modernize/impl/ModernizeErrorProvider.java
author Ilia Gromov <ilia@netbeans.org>
Thu, 15 Jun 2017 13:26:38 +0300
branchrelease82
changeset 18425 4b288c339c55
parent 18415 35b6125ef00c
child 18426 76cdf4401581
permissions -rw-r--r--
[clang-tidy] merge analyser errors (only DEV compatible)
(transplanted from 853976f2c6166dbb19b482e2247ec824b7183371)
     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * If you wish your version of this file to be governed by only the CDDL
    28  * or only the GPL Version 2, indicate your decision by adding
    29  * "[Contributor] elects to include this software in this distribution
    30  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    31  * single choice of license, a recipient has the option to distribute
    32  * your version of this file under either the CDDL, the GPL Version 2 or
    33  * to extend the choice of license to its licensees as provided above.
    34  * However, if you add GPL Version 2 code and therefore, elected the GPL
    35  * Version 2 license, then the option applies only if the new code is
    36  * made subject to such option by the copyright holder.
    37  *
    38  * Contributor(s): Ilia Gromov
    39  */
    40 package com.sun.tools.ide.analysis.modernize.impl;
    41 
    42 import com.sun.tools.ide.analysis.modernize.impl.YamlParser.Replacement;
    43 import com.sun.tools.ide.analysis.modernize.options.AnalyzerPreferences;
    44 import com.sun.tools.ide.analysis.modernize.options.ClangAnalyzerOptions;
    45 import static com.sun.tools.ide.analysis.modernize.utils.AnalyticsTools.fatalError;
    46 import static com.sun.tools.ide.analysis.modernize.utils.AnalyticsTools.findItem;
    47 import java.io.File;
    48 import java.io.IOException;
    49 import java.util.ArrayList;
    50 import java.util.Collection;
    51 import java.util.Collections;
    52 import java.util.List;
    53 import java.util.logging.Level;
    54 import java.util.logging.Logger;
    55 import java.util.prefs.Preferences;
    56 import javax.swing.JComponent;
    57 import javax.swing.JLabel;
    58 import org.netbeans.modules.cnd.analysis.api.AbstractCustomizerProvider;
    59 import org.netbeans.modules.cnd.analysis.api.AnalyzerResponse;
    60 import org.netbeans.modules.cnd.api.model.CsmFile;
    61 import org.netbeans.modules.cnd.api.model.syntaxerr.AbstractCodeAudit;
    62 import org.netbeans.modules.cnd.api.model.syntaxerr.AuditPreferences;
    63 import org.netbeans.modules.cnd.api.model.syntaxerr.CodeAudit;
    64 import org.netbeans.modules.cnd.api.model.syntaxerr.CodeAuditFactory;
    65 import org.netbeans.modules.cnd.api.model.syntaxerr.CodeAuditProvider;
    66 import org.netbeans.modules.cnd.api.model.syntaxerr.CsmErrorInfo;
    67 import org.netbeans.modules.cnd.api.model.syntaxerr.CsmErrorInfo.Severity;
    68 import org.netbeans.modules.cnd.api.model.syntaxerr.CsmErrorInfoHintProvider;
    69 import org.netbeans.modules.cnd.api.model.syntaxerr.CsmErrorProvider;
    70 import org.netbeans.modules.cnd.api.project.NativeFileItem.Language;
    71 import org.netbeans.modules.cnd.api.project.NativeProject;
    72 import org.netbeans.modules.cnd.api.remote.RemoteProject;
    73 import org.netbeans.modules.cnd.makeproject.api.MakeProject;
    74 import org.netbeans.modules.cnd.makeproject.api.configurations.Item;
    75 import org.netbeans.modules.cnd.modelutil.CsmUtilities;
    76 import org.netbeans.modules.cnd.utils.MIMENames;
    77 import org.netbeans.modules.nativeexecution.api.ExecutionEnvironment;
    78 import org.netbeans.modules.nativeexecution.api.ExecutionEnvironmentFactory;
    79 import org.netbeans.modules.nativeexecution.api.util.ConnectionManager;
    80 import org.netbeans.spi.editor.hints.ErrorDescription;
    81 import org.netbeans.spi.editor.hints.Fix;
    82 import org.openide.filesystems.FileObject;
    83 import org.openide.filesystems.FileUtil;
    84 import org.openide.util.Exceptions;
    85 import org.openide.util.Lookup;
    86 import org.openide.util.NbBundle;
    87 import org.openide.util.lookup.ServiceProvider;
    88 import org.openide.util.lookup.ServiceProviders;
    89 
    90 @ServiceProviders({
    91     @ServiceProvider(service = CsmErrorProvider.class, position = 2100)
    92     ,
    93     @ServiceProvider(service = CodeAuditProvider.class, position = 2100)
    94 })
    95 public final class ModernizeErrorProvider extends CsmErrorProvider implements CodeAuditProvider, AbstractCustomizerProvider {
    96 
    97     public static final Logger LOG = Logger.getLogger("ide.analysis.tidy"); //NOI18N
    98     public static final String NAME = "Modernize"; //NOI18N
    99 
   100     private Collection<CodeAudit> audits;
   101     private AnalyzerResponseMerger analyzerResponseMerger;
   102 
   103     public static ModernizeErrorProvider getInstance() {
   104         for (CsmErrorProvider provider : Lookup.getDefault().lookupAll(CsmErrorProvider.class)) {
   105             if (NAME.equals(provider.getName()) && provider instanceof ModernizeErrorProvider) {
   106                 return (ModernizeErrorProvider) provider;
   107             }
   108         }
   109         return null;
   110     }
   111 
   112     @Override
   113     protected boolean validate(Request request) {
   114         CsmFile file = request.getFile();
   115         return file != null;
   116     }
   117 
   118     @Override
   119     public boolean hasHintControlPanel() {
   120         return true;
   121     }
   122 
   123     @Override
   124     public String getName() {
   125         return NAME;
   126     }
   127 
   128     @Override
   129     public String getDisplayName() {
   130         return NbBundle.getMessage(ModernizeErrorProvider.class, "Modernize_NAME"); //NOI18N
   131     }
   132 
   133     @Override
   134     public String getDescription() {
   135         return NbBundle.getMessage(ModernizeErrorProvider.class, "Modernize_DESCRIPTION"); //NOI18N
   136     }
   137 
   138     @Override
   139     public String getMimeType() {
   140         return MIMENames.SOURCES_MIME_TYPE;
   141     }
   142 
   143     @Override
   144     public boolean isSupportedEvent(EditorEvent kind) {
   145         return kind == EditorEvent.FileBased;
   146     }
   147 
   148     @Override
   149     protected void doGetErrors(CsmErrorProvider.Request request, CsmErrorProvider.Response response) {
   150         CsmFile file = request.getFile();
   151         if (file != null) {
   152             if (request.isCancelled()) {
   153                 return;
   154             }
   155             Object platformProject = file.getProject().getPlatformProject();
   156             if (platformProject instanceof NativeProject) {
   157                 Lookup.Provider project = ((NativeProject) platformProject).getProject();
   158                 if (project != null) {
   159                     if (request.isCancelled()) {
   160                         return;
   161                     }
   162                     Thread currentThread = Thread.currentThread();
   163                     currentThread.setName("Provider " + getName() + " prosess " + file.getAbsolutePath()); // NOI18N
   164                     RemoteProject info = project.getLookup().lookup(RemoteProject.class);
   165                     if (info != null) {
   166                         ExecutionEnvironment execEnv = info.getDevelopmentHost();
   167                         if (execEnv != null) {
   168                             if (request.isCancelled()) {
   169                                 return;
   170                             }
   171                             if (ConnectionManager.getInstance().isConnectedTo(execEnv)) {
   172                                 Item item = findItem(file, project);
   173                                 if (item != null) {
   174                                     if (request.isCancelled()) {
   175                                         return;
   176                                     }
   177                                     // Temporarily analyzing even excluded items
   178                                     if (/* !item.isExcluded() &&  */(item.getLanguage() == Language.C || item.getLanguage() == Language.CPP || item.getLanguage() == Language.C_HEADER)) {
   179                                         analyze(execEnv, item, project, request, response);
   180                                     }
   181                                 }
   182                             }
   183                         }
   184                     }
   185                 }
   186             }
   187         }
   188     }
   189 
   190     public void analyze(ExecutionEnvironment execEnv, Item item, Lookup.Provider project, CsmErrorProvider.Request request, CsmErrorProvider.Response response) {
   191         String binaryPath = ClangAnalyzerOptions.getClangAnalyzerPath();
   192         boolean isAnalyzer = response instanceof ModernizeAnalyzerImpl.ModernizeResponse;
   193         if (binaryPath == null) {
   194             Level level = isAnalyzer ? Level.INFO : Level.FINE;
   195             LOG.log(level, "clang-tidy needs to be installed as a plugin"); //NOI18N
   196             return;
   197         }
   198 
   199         if (isAnalyzer && isNewRun()) {
   200             AnalyzedFiles.getDefault().clear();
   201             analyzerResponseMerger = new AnalyzerResponseMerger((ModernizeAnalyzerImpl.ModernizeResponse) response);
   202         }
   203 
   204         DiagnosticsTool diagnosticsTool = new DiagnosticsTool(execEnv, item, (MakeProject) project, binaryPath);
   205         try {
   206             CsmFile csmFile = request.getFile();
   207             Collection<String> checks = /*isAnalyzer ? Collections.singleton("*") : */ getEnabledChecks(); //NOI18N
   208 
   209             Collection<CsmFile> tu = new ArrayList<CsmFile>();
   210             if (isAnalyzer) {
   211                 tu.add(csmFile);
   212             } else {
   213                 if (AnalyzedFiles.getDefault().isStartFile(csmFile)) {
   214                     tu.add(csmFile);
   215                 } else {
   216                     tu.addAll(AnalyzedFiles.getDefault().getStartFiles(csmFile));
   217                 }
   218             }
   219 
   220             if (!isAnalyzer) {
   221                 response = new CsmResponseMerger(response);
   222             }
   223 
   224             for (CsmFile startFile : tu) {
   225                 int exitCode = diagnosticsTool.process(checks, startFile, true);
   226                 if (exitCode != DiagnosticsTool.STATUS_OK) {
   227                     String error = NbBundle.getMessage(ModernizeErrorProvider.class, "compile.file.error"); //NOI18N
   228                     String info = NbBundle.getMessage(ModernizeErrorProvider.class, "compile.file.error.info", "" + exitCode); //NOI18N
   229                     fatalError(AnalyzerResponse.AnalyzerSeverity.FileError, "fatal.analyze.error", error + "\n" + info, csmFile, response); //NOI18N
   230                     return;
   231                 }
   232                 List<YamlParser.Diagnostics> results = YamlParser.getDefault().parseYaml(diagnosticsTool.getYamlAsString());
   233                 postProcess(isAnalyzer, startFile, project, results, request, response);
   234             }
   235 
   236             if (!isAnalyzer) {
   237                 response.done();
   238             }
   239 
   240         } catch (ConnectionManager.CancellationException | IOException ex) {
   241             Exceptions.printStackTrace(ex);
   242         }
   243     }
   244 
   245     private static CsmErrorProvider last;
   246 
   247     private boolean isNewRun() {
   248         if (last == null || this != last) {
   249             last = this;
   250             return true;
   251         }
   252         return false;
   253     }
   254 
   255     public Collection<ErrorDescription> done() {
   256         return analyzerResponseMerger.done();
   257     }
   258 
   259     public void postProcess(boolean isAnalyzer, CsmFile startFile, Lookup.Provider project, List<YamlParser.Diagnostics> results, CsmErrorProvider.Request request, CsmErrorProvider.Response response) {
   260         CsmFile file = request.getFile();
   261         List<CsmFile> otherCsmFiles = new ArrayList<>();
   262 
   263         for (YamlParser.Diagnostics diag : results) {
   264             // TODO: don't add "Configure Hint" fix multiple times for one line
   265             FileObject fo = FileUtil.toFileObject(new File(diag.getMessageFilePath()));
   266             CsmFile csmFile = CsmUtilities.getCsmFile(fo, false, false);
   267 
   268             // Composing a preview message. Showing a start file for compilation unit
   269             // in case we analysing a header file
   270             ModernizeErrorInfo info;
   271             if (startFile.equals(file) && csmFile.equals(file)) {
   272                 String message = String.format("[%s]: %s", diag.getCheckName(), diag.getMessage()); //NOI18N
   273                 info = ModernizeErrorInfo.withFixedMessage(diag, message, project);
   274             } else {
   275                 info = ModernizeErrorInfo.withMutableMessage(diag, diag.getCheckName(), startFile.getName().toString(), diag.getMessage(), project);
   276             }
   277 
   278             if (isAnalyzer) {
   279                 // Add found errors for all files (can be other files from compileUnit)
   280                 analyzerResponseMerger.addError(info, fo);
   281 
   282                 if (!csmFile.equals(file)) {
   283                     // May be not header (e.g BBB.cc: AAA.cc -> (includes) BBB.cc -> ... )
   284                     otherCsmFiles.add(csmFile);
   285                 }
   286             } else if (fo.equals(file.getFileObject())) {
   287                 // Add found errors only for file displayed in Editor
   288                 response.addError(info);
   289             }
   290         }
   291 
   292         if (isAnalyzer /* and not empty? */) {
   293             AnalyzedFiles.getDefault().cacheHierarchy(file, otherCsmFiles);
   294         }
   295     }
   296 
   297     @ServiceProvider(path = CodeAuditFactory.REGISTRATION_PATH + ModernizeErrorProvider.NAME, service = CodeAuditFactory.class, position = 4000)
   298     public static final class Factory implements CodeAuditFactory {
   299 
   300         @Override
   301         public AbstractCodeAudit create(AuditPreferences preferences) {
   302             String id = NbBundle.getMessage(ModernizeCodeAudit.class, "LBL_ProviderName");  // NOI18N
   303             String description = NbBundle.getMessage(ModernizeCodeAudit.class, "LBL_ProviderDescription");  // NOI18N
   304             return new ModernizeCodeAudit(id, id, description, "error", false, preferences);  // NOI18N
   305         }
   306     }
   307 
   308     private String oldPath;
   309 
   310     @Override
   311     public synchronized Collection<CodeAudit> getAudits() {
   312         String path = ClangAnalyzerOptions.getClangAnalyzerPath();
   313 
   314         if (path == null) {
   315             return Collections.emptyList();
   316         }
   317 
   318         if (oldPath == null) {
   319             oldPath = path;
   320         }
   321 
   322         if (audits == null || !oldPath.equals(path)) {
   323             List<CodeAudit> res = DiagnosticsTool.getAudits(path, ExecutionEnvironmentFactory.getLocal(), AnalyzerPreferences.getAuditPreferences());
   324 
   325             audits = res;
   326             oldPath = path;
   327         }
   328         return audits;
   329     }
   330 
   331     public Collection<String> getEnabledChecks() {
   332         Collection<CodeAudit> auditList = getAudits();
   333         List<String> enabled = new ArrayList<String>();
   334         for (CodeAudit codeAudit : auditList) {
   335             if (codeAudit.isEnabled()) {
   336                 enabled.add(codeAudit.getID());
   337             }
   338         }
   339         return enabled.size() == auditList.size() ? Collections.singleton("*") : enabled; //NOI18N
   340     }
   341 
   342     @Override
   343     public AuditPreferences getPreferences() {
   344         return AnalyzerPreferences.getAuditPreferences();
   345     }
   346 
   347     @Override
   348     public JComponent createComponent(Preferences context) {
   349         return new JLabel();
   350     }
   351 
   352     public static interface ErrorInfoWithId {
   353 
   354         String getId();
   355     }
   356 
   357     public static final class FatalErrorInfo implements CsmErrorInfo, ErrorInfoWithId {
   358 
   359         private final String id;
   360         private final String message;
   361 
   362         public FatalErrorInfo(String id, String message) {
   363             this.id = id;
   364             this.message = message;
   365         }
   366 
   367         @Override
   368         public String getMessage() {
   369             return message;
   370         }
   371 
   372         @Override
   373         public Severity getSeverity() {
   374             return Severity.WARNING;
   375         }
   376 
   377         @Override
   378         public int getStartOffset() {
   379             return 0;
   380         }
   381 
   382         @Override
   383         public int getEndOffset() {
   384             return 1;
   385         }
   386 
   387         @Override
   388         public String getId() {
   389             return id;
   390         }
   391     }
   392 
   393     @ServiceProvider(service = CsmErrorInfoHintProvider.class, position = 9100)
   394     public final static class ModerinzeHintProvider extends CsmErrorInfoHintProvider {
   395 
   396         @Override
   397         protected List<Fix> doGetFixes(CsmErrorInfo info, List<Fix> alreadyFound) {
   398             if (info instanceof ModernizeErrorInfo) {
   399                 alreadyFound.add(new ConfigureHintsFix((ModernizeErrorInfo) info));
   400             }
   401             return alreadyFound;
   402         }
   403     }
   404 
   405     @ServiceProvider(service = CsmErrorInfoHintProvider.class, position = 1600)
   406     public static final class ModernizeFixProvider extends CsmErrorInfoHintProvider {
   407 
   408         @Override
   409         protected List<Fix> doGetFixes(CsmErrorInfo info, List<Fix> alreadyFound) {
   410             alreadyFound.addAll(createFixes(info));
   411             return alreadyFound;
   412         }
   413     }
   414 
   415     private static List<? extends Fix> createFixes(CsmErrorInfo info) {
   416         if (info instanceof ModernizeErrorInfo) {
   417             ModernizeErrorInfo mei = (ModernizeErrorInfo) info;
   418             List<Replacement> replacements = mei.getDiagnostics().getReplacements();
   419             if (!replacements.isEmpty()) {
   420                 return Collections.singletonList(new ModernizeFix(replacements, mei.getId()));
   421             }
   422         }
   423         return Collections.EMPTY_LIST;
   424     }
   425 }