c.s.tools.ide.analysis.modernize/src/com/sun/tools/ide/analysis/modernize/impl/ModernizeErrorProvider.java
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
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]"
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.
38 * Contributor(s): Ilia Gromov
40 package com.sun.tools.ide.analysis.modernize.impl;
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;
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;
91 @ServiceProvider(service = CsmErrorProvider.class, position = 2100)
93 @ServiceProvider(service = CodeAuditProvider.class, position = 2100)
95 public final class ModernizeErrorProvider extends CsmErrorProvider implements CodeAuditProvider, AbstractCustomizerProvider {
97 public static final Logger LOG = Logger.getLogger("ide.analysis.tidy"); //NOI18N
98 public static final String NAME = "Modernize"; //NOI18N
100 private Collection<CodeAudit> audits;
101 private AnalyzerResponseMerger analyzerResponseMerger;
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;
113 protected boolean validate(Request request) {
114 CsmFile file = request.getFile();
119 public boolean hasHintControlPanel() {
124 public String getName() {
129 public String getDisplayName() {
130 return NbBundle.getMessage(ModernizeErrorProvider.class, "Modernize_NAME"); //NOI18N
134 public String getDescription() {
135 return NbBundle.getMessage(ModernizeErrorProvider.class, "Modernize_DESCRIPTION"); //NOI18N
139 public String getMimeType() {
140 return MIMENames.SOURCES_MIME_TYPE;
144 public boolean isSupportedEvent(EditorEvent kind) {
145 return kind == EditorEvent.FileBased;
149 protected void doGetErrors(CsmErrorProvider.Request request, CsmErrorProvider.Response response) {
150 CsmFile file = request.getFile();
152 if (request.isCancelled()) {
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()) {
162 Thread currentThread = Thread.currentThread();
163 currentThread.setName("Provider " + getName() + " prosess " + file.getAbsolutePath()); // NOI18N
164 RemoteProject info = project.getLookup().lookup(RemoteProject.class);
166 ExecutionEnvironment execEnv = info.getDevelopmentHost();
167 if (execEnv != null) {
168 if (request.isCancelled()) {
171 if (ConnectionManager.getInstance().isConnectedTo(execEnv)) {
172 Item item = findItem(file, project);
174 if (request.isCancelled()) {
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);
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
199 if (isAnalyzer && isNewRun()) {
200 AnalyzedFiles.getDefault().clear();
201 analyzerResponseMerger = new AnalyzerResponseMerger((ModernizeAnalyzerImpl.ModernizeResponse) response);
204 DiagnosticsTool diagnosticsTool = new DiagnosticsTool(execEnv, item, (MakeProject) project, binaryPath);
206 CsmFile csmFile = request.getFile();
207 Collection<String> checks = /*isAnalyzer ? Collections.singleton("*") : */ getEnabledChecks(); //NOI18N
209 Collection<CsmFile> tu = new ArrayList<CsmFile>();
213 if (AnalyzedFiles.getDefault().isStartFile(csmFile)) {
216 tu.addAll(AnalyzedFiles.getDefault().getStartFiles(csmFile));
221 response = new CsmResponseMerger(response);
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
232 List<YamlParser.Diagnostics> results = YamlParser.getDefault().parseYaml(diagnosticsTool.getYamlAsString());
233 postProcess(isAnalyzer, startFile, project, results, request, response);
240 } catch (ConnectionManager.CancellationException | IOException ex) {
241 Exceptions.printStackTrace(ex);
245 private static CsmErrorProvider last;
247 private boolean isNewRun() {
248 if (last == null || this != last) {
255 public Collection<ErrorDescription> done() {
256 return analyzerResponseMerger.done();
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<>();
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);
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);
275 info = ModernizeErrorInfo.withMutableMessage(diag, diag.getCheckName(), startFile.getName().toString(), diag.getMessage(), project);
279 // Add found errors for all files (can be other files from compileUnit)
280 analyzerResponseMerger.addError(info, fo);
282 if (!csmFile.equals(file)) {
283 // May be not header (e.g BBB.cc: AAA.cc -> (includes) BBB.cc -> ... )
284 otherCsmFiles.add(csmFile);
286 } else if (fo.equals(file.getFileObject())) {
287 // Add found errors only for file displayed in Editor
288 response.addError(info);
292 if (isAnalyzer /* and not empty? */) {
293 AnalyzedFiles.getDefault().cacheHierarchy(file, otherCsmFiles);
297 @ServiceProvider(path = CodeAuditFactory.REGISTRATION_PATH + ModernizeErrorProvider.NAME, service = CodeAuditFactory.class, position = 4000)
298 public static final class Factory implements CodeAuditFactory {
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
308 private String oldPath;
311 public synchronized Collection<CodeAudit> getAudits() {
312 String path = ClangAnalyzerOptions.getClangAnalyzerPath();
315 return Collections.emptyList();
318 if (oldPath == null) {
322 if (audits == null || !oldPath.equals(path)) {
323 List<CodeAudit> res = DiagnosticsTool.getAudits(path, ExecutionEnvironmentFactory.getLocal(), AnalyzerPreferences.getAuditPreferences());
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());
339 return enabled.size() == auditList.size() ? Collections.singleton("*") : enabled; //NOI18N
343 public AuditPreferences getPreferences() {
344 return AnalyzerPreferences.getAuditPreferences();
348 public JComponent createComponent(Preferences context) {
352 public static interface ErrorInfoWithId {
357 public static final class FatalErrorInfo implements CsmErrorInfo, ErrorInfoWithId {
359 private final String id;
360 private final String message;
362 public FatalErrorInfo(String id, String message) {
364 this.message = message;
368 public String getMessage() {
373 public Severity getSeverity() {
374 return Severity.WARNING;
378 public int getStartOffset() {
383 public int getEndOffset() {
388 public String getId() {
393 @ServiceProvider(service = CsmErrorInfoHintProvider.class, position = 9100)
394 public final static class ModerinzeHintProvider extends CsmErrorInfoHintProvider {
397 protected List<Fix> doGetFixes(CsmErrorInfo info, List<Fix> alreadyFound) {
398 if (info instanceof ModernizeErrorInfo) {
399 alreadyFound.add(new ConfigureHintsFix((ModernizeErrorInfo) info));
405 @ServiceProvider(service = CsmErrorInfoHintProvider.class, position = 1600)
406 public static final class ModernizeFixProvider extends CsmErrorInfoHintProvider {
409 protected List<Fix> doGetFixes(CsmErrorInfo info, List<Fix> alreadyFound) {
410 alreadyFound.addAll(createFixes(info));
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()));
423 return Collections.EMPTY_LIST;