c.s.tools.ide.analysis.modernize/src/com/sun/tools/ide/analysis/modernize/impl/ModernizeErrorProvider.java
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/c.s.tools.ide.analysis.modernize/src/com/sun/tools/ide/analysis/modernize/impl/ModernizeErrorProvider.java Wed Jun 07 20:23:29 2017 +0300
1.3 @@ -0,0 +1,420 @@
1.4 +/*
1.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
1.6 + *
1.7 + * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
1.8 + *
1.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
1.10 + * Other names may be trademarks of their respective owners.
1.11 + *
1.12 + * The contents of this file are subject to the terms of either the GNU
1.13 + * General Public License Version 2 only ("GPL") or the Common
1.14 + * Development and Distribution License("CDDL") (collectively, the
1.15 + * "License"). You may not use this file except in compliance with the
1.16 + * License. You can obtain a copy of the License at
1.17 + * http://www.netbeans.org/cddl-gplv2.html
1.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
1.19 + * specific language governing permissions and limitations under the
1.20 + * License. When distributing the software, include this License Header
1.21 + * Notice in each file and include the License file at
1.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
1.23 + * particular file as subject to the "Classpath" exception as provided
1.24 + * by Oracle in the GPL Version 2 section of the License file that
1.25 + * accompanied this code. If applicable, add the following below the
1.26 + * License Header, with the fields enclosed by brackets [] replaced by
1.27 + * your own identifying information:
1.28 + * "Portions Copyrighted [year] [name of copyright owner]"
1.29 + *
1.30 + * If you wish your version of this file to be governed by only the CDDL
1.31 + * or only the GPL Version 2, indicate your decision by adding
1.32 + * "[Contributor] elects to include this software in this distribution
1.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
1.34 + * single choice of license, a recipient has the option to distribute
1.35 + * your version of this file under either the CDDL, the GPL Version 2 or
1.36 + * to extend the choice of license to its licensees as provided above.
1.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
1.38 + * Version 2 license, then the option applies only if the new code is
1.39 + * made subject to such option by the copyright holder.
1.40 + *
1.41 + * Contributor(s): Ilia Gromov
1.42 + */
1.43 +package com.sun.tools.ide.analysis.modernize.impl;
1.44 +
1.45 +import com.sun.tools.ide.analysis.modernize.impl.ModernizeAnalyzerImpl.ResponseImpl;
1.46 +import com.sun.tools.ide.analysis.modernize.impl.YamlParser.Replacement;
1.47 +import com.sun.tools.ide.analysis.modernize.options.AnalyzerPreferences;
1.48 +import com.sun.tools.ide.analysis.modernize.options.ClangAnalyzerOptions;
1.49 +import static com.sun.tools.ide.analysis.modernize.utils.AnalyticsTools.fatalError;
1.50 +import static com.sun.tools.ide.analysis.modernize.utils.AnalyticsTools.findItem;
1.51 +import java.io.File;
1.52 +import java.io.IOException;
1.53 +import java.util.ArrayList;
1.54 +import java.util.Collection;
1.55 +import java.util.Collections;
1.56 +import java.util.List;
1.57 +import java.util.logging.Level;
1.58 +import java.util.logging.Logger;
1.59 +import java.util.prefs.Preferences;
1.60 +import javax.swing.JComponent;
1.61 +import javax.swing.JLabel;
1.62 +import org.netbeans.modules.cnd.analysis.api.AbstractCustomizerProvider;
1.63 +import org.netbeans.modules.cnd.analysis.api.AnalyzerResponse;
1.64 +import org.netbeans.modules.cnd.api.model.CsmFile;
1.65 +import org.netbeans.modules.cnd.api.model.syntaxerr.AbstractCodeAudit;
1.66 +import org.netbeans.modules.cnd.api.model.syntaxerr.AuditPreferences;
1.67 +import org.netbeans.modules.cnd.api.model.syntaxerr.CodeAudit;
1.68 +import org.netbeans.modules.cnd.api.model.syntaxerr.CodeAuditFactory;
1.69 +import org.netbeans.modules.cnd.api.model.syntaxerr.CodeAuditProvider;
1.70 +import org.netbeans.modules.cnd.api.model.syntaxerr.CsmErrorInfo;
1.71 +import org.netbeans.modules.cnd.api.model.syntaxerr.CsmErrorInfo.Severity;
1.72 +import org.netbeans.modules.cnd.api.model.syntaxerr.CsmErrorInfoHintProvider;
1.73 +import org.netbeans.modules.cnd.api.model.syntaxerr.CsmErrorProvider;
1.74 +import org.netbeans.modules.cnd.api.project.NativeFileItem.Language;
1.75 +import org.netbeans.modules.cnd.api.project.NativeProject;
1.76 +import org.netbeans.modules.cnd.api.remote.RemoteProject;
1.77 +import org.netbeans.modules.cnd.makeproject.api.MakeProject;
1.78 +import org.netbeans.modules.cnd.makeproject.api.configurations.Item;
1.79 +import org.netbeans.modules.cnd.modelutil.CsmUtilities;
1.80 +import org.netbeans.modules.cnd.utils.MIMENames;
1.81 +import org.netbeans.modules.nativeexecution.api.ExecutionEnvironment;
1.82 +import org.netbeans.modules.nativeexecution.api.ExecutionEnvironmentFactory;
1.83 +import org.netbeans.modules.nativeexecution.api.util.ConnectionManager;
1.84 +import org.netbeans.spi.editor.hints.Fix;
1.85 +import org.openide.filesystems.FileObject;
1.86 +import org.openide.filesystems.FileUtil;
1.87 +import org.openide.util.Exceptions;
1.88 +import org.openide.util.Lookup;
1.89 +import org.openide.util.NbBundle;
1.90 +import org.openide.util.lookup.ServiceProvider;
1.91 +import org.openide.util.lookup.ServiceProviders;
1.92 +
1.93 +@ServiceProviders({
1.94 + @ServiceProvider(service = CsmErrorProvider.class, position = 2100)
1.95 + ,
1.96 + @ServiceProvider(service = CodeAuditProvider.class, position = 2100)
1.97 +})
1.98 +public final class ModernizeErrorProvider extends CsmErrorProvider implements CodeAuditProvider, AbstractCustomizerProvider {
1.99 +
1.100 + public static final Logger LOG = Logger.getLogger("ide.analysis.tidy"); //NOI18N
1.101 + private Collection<CodeAudit> audits;
1.102 + public static final String NAME = "Modernize"; //NOI18N
1.103 +
1.104 + public static ModernizeErrorProvider getInstance() {
1.105 + for (CsmErrorProvider provider : Lookup.getDefault().lookupAll(CsmErrorProvider.class)) {
1.106 + if (NAME.equals(provider.getName()) && provider instanceof ModernizeErrorProvider) {
1.107 + return (ModernizeErrorProvider) provider;
1.108 + }
1.109 + }
1.110 + return null;
1.111 + }
1.112 +
1.113 + @Override
1.114 + protected boolean validate(Request request) {
1.115 + CsmFile file = request.getFile();
1.116 + return file != null;
1.117 + }
1.118 +
1.119 + @Override
1.120 + public boolean hasHintControlPanel() {
1.121 + return true;
1.122 + }
1.123 +
1.124 + @Override
1.125 + public String getName() {
1.126 + return NAME;
1.127 + }
1.128 +
1.129 + @Override
1.130 + public String getDisplayName() {
1.131 + return NbBundle.getMessage(ModernizeErrorProvider.class, "Modernize_NAME"); //NOI18N
1.132 + }
1.133 +
1.134 + @Override
1.135 + public String getDescription() {
1.136 + return NbBundle.getMessage(ModernizeErrorProvider.class, "Modernize_DESCRIPTION"); //NOI18N
1.137 + }
1.138 +
1.139 + @Override
1.140 + public String getMimeType() {
1.141 + return MIMENames.SOURCES_MIME_TYPE;
1.142 + }
1.143 +
1.144 + @Override
1.145 + public boolean isSupportedEvent(EditorEvent kind) {
1.146 + return kind == EditorEvent.FileBased;
1.147 + }
1.148 +
1.149 + @Override
1.150 + protected void doGetErrors(CsmErrorProvider.Request request, CsmErrorProvider.Response response) {
1.151 + CsmFile file = request.getFile();
1.152 + if (file != null) {
1.153 + if (request.isCancelled()) {
1.154 + return;
1.155 + }
1.156 + Object platformProject = file.getProject().getPlatformProject();
1.157 + if (platformProject instanceof NativeProject) {
1.158 + Lookup.Provider project = ((NativeProject) platformProject).getProject();
1.159 + if (project != null) {
1.160 + if (request.isCancelled()) {
1.161 + return;
1.162 + }
1.163 + Thread currentThread = Thread.currentThread();
1.164 + currentThread.setName("Provider " + getName() + " prosess " + file.getAbsolutePath()); // NOI18N
1.165 + RemoteProject info = project.getLookup().lookup(RemoteProject.class);
1.166 + if (info != null) {
1.167 + ExecutionEnvironment execEnv = info.getDevelopmentHost();
1.168 + if (execEnv != null) {
1.169 + if (request.isCancelled()) {
1.170 + return;
1.171 + }
1.172 + if (ConnectionManager.getInstance().isConnectedTo(execEnv)) {
1.173 + Item item = findItem(file, project);
1.174 + if (item != null) {
1.175 + if (request.isCancelled()) {
1.176 + return;
1.177 + }
1.178 + // Temporarily analyzing even excluded items
1.179 + if (/* !item.isExcluded() && */(item.getLanguage() == Language.C || item.getLanguage() == Language.CPP || item.getLanguage() == Language.C_HEADER)) {
1.180 + analyze(execEnv, item, project, request, response);
1.181 + }
1.182 + }
1.183 + }
1.184 + }
1.185 + }
1.186 + }
1.187 + }
1.188 + }
1.189 + }
1.190 +
1.191 + public void analyze(ExecutionEnvironment execEnv, Item item, Lookup.Provider project, CsmErrorProvider.Request request, CsmErrorProvider.Response response) {
1.192 + String binaryPath = ClangAnalyzerOptions.getClangAnalyzerPath();
1.193 + boolean isAnalyzer = response instanceof ModernizeAnalyzerImpl.ResponseImpl;
1.194 + if (binaryPath == null) {
1.195 + Level level = isAnalyzer ? Level.INFO : Level.FINE;
1.196 + LOG.log(level, "clang-tidy needs to be installed as a plugin"); //NOI18N
1.197 + return;
1.198 + }
1.199 +
1.200 + if (isAnalyzer && isNewRun()) {
1.201 + AnalyzedFiles.getDefault().clear();
1.202 + }
1.203 +
1.204 + DiagnosticsTool diagnosticsTool = new DiagnosticsTool(execEnv, item, (MakeProject) project, binaryPath);
1.205 + try {
1.206 + CsmFile csmFile = request.getFile();
1.207 + Collection<String> checks = /*isAnalyzer ? Collections.singleton("*") : */ getEnabledChecks(); //NOI18N
1.208 +
1.209 + Collection<CsmFile> tu = new ArrayList<CsmFile>();
1.210 + if (isAnalyzer) {
1.211 + tu.add(csmFile);
1.212 + } else {
1.213 + if (AnalyzedFiles.getDefault().isStartFile(csmFile)) {
1.214 + tu.add(csmFile);
1.215 + } else {
1.216 + tu.addAll(AnalyzedFiles.getDefault().getStartFiles(csmFile));
1.217 + }
1.218 + }
1.219 +
1.220 + if (!isAnalyzer) {
1.221 + response = new ResponseMerger(response);
1.222 + }
1.223 +
1.224 + for (CsmFile startFile : tu) {
1.225 + int exitCode = diagnosticsTool.process(checks, startFile, true);
1.226 + if (exitCode != DiagnosticsTool.STATUS_OK) {
1.227 + String error = NbBundle.getMessage(ModernizeErrorProvider.class, "compile.file.error"); //NOI18N
1.228 + String info = NbBundle.getMessage(ModernizeErrorProvider.class, "compile.file.error.info", "" + exitCode); //NOI18N
1.229 + fatalError(AnalyzerResponse.AnalyzerSeverity.FileError, "fatal.analyze.error", error + "\n" + info, csmFile, response); //NOI18N
1.230 + return;
1.231 + }
1.232 + List<YamlParser.Diagnostics> results = YamlParser.getDefault().parseYaml(diagnosticsTool.getYamlAsString());
1.233 + postProcess(isAnalyzer, startFile, project, results, request, response);
1.234 + }
1.235 +
1.236 + if (!isAnalyzer) {
1.237 + response.done();
1.238 + }
1.239 +
1.240 + } catch (ConnectionManager.CancellationException ex) {
1.241 + Exceptions.printStackTrace(ex);
1.242 + } catch (IOException ex) {
1.243 + Exceptions.printStackTrace(ex);
1.244 + }
1.245 + }
1.246 +
1.247 + private static CsmErrorProvider last;
1.248 +
1.249 + private boolean isNewRun() {
1.250 + if (last == null || this != last) {
1.251 + last = this;
1.252 + return true;
1.253 + }
1.254 + return false;
1.255 + }
1.256 +
1.257 + public void postProcess(boolean isAnalyzer, CsmFile startFile, Lookup.Provider project, List<YamlParser.Diagnostics> results, CsmErrorProvider.Request request, CsmErrorProvider.Response response) {
1.258 + CsmFile file = request.getFile();
1.259 + List<CsmFile> otherCsmFiles = new ArrayList<CsmFile>();
1.260 +
1.261 + for (YamlParser.Diagnostics diag : results) {
1.262 + // TODO: don't add "Configure Hint" fix multiple times for one line
1.263 + FileObject fo = FileUtil.toFileObject(new File(diag.getMessageFilePath()));
1.264 + CsmFile csmFile = CsmUtilities.getCsmFile(fo, false, false);
1.265 +
1.266 + // Composing a preview message. Showing a start file for compilation unit
1.267 + // in case we analysing a header file
1.268 + ModernizeErrorInfo info;
1.269 + if (startFile.equals(file) && csmFile.equals(file)) {
1.270 + String message = String.format("[%s]: %s", diag.getCheckName(), diag.getMessage()); //NOI18N
1.271 + info = ModernizeErrorInfo.withFixedMessage(diag, message, project);
1.272 + } else {
1.273 + info = ModernizeErrorInfo.withMutableMessage(diag, diag.getCheckName(), startFile.getName().toString(), diag.getMessage(), project);
1.274 + }
1.275 +
1.276 + if (isAnalyzer) {
1.277 + // Add found errors for all files (can be other files from compileUnit)
1.278 + ((ResponseImpl) response).addError(AnalyzerResponse.AnalyzerSeverity.DetectedError, null, fo, info);
1.279 +
1.280 + if (!csmFile.equals(file)) {
1.281 + // May be not header (e.g BBB.cc: AAA.cc -> (includes) BBB.cc -> ... )
1.282 + otherCsmFiles.add(csmFile);
1.283 + }
1.284 + } else if (fo.equals(file.getFileObject())) {
1.285 + // Add found errors only for file displayed in Editor
1.286 + response.addError(info);
1.287 + }
1.288 + }
1.289 +
1.290 + if (isAnalyzer /* and not empty? */) {
1.291 + AnalyzedFiles.getDefault().cacheHierarchy(file, otherCsmFiles);
1.292 + }
1.293 + }
1.294 +
1.295 + @ServiceProvider(path = CodeAuditFactory.REGISTRATION_PATH + ModernizeErrorProvider.NAME, service = CodeAuditFactory.class, position = 4000)
1.296 + public static final class Factory implements CodeAuditFactory {
1.297 +
1.298 + @Override
1.299 + public AbstractCodeAudit create(AuditPreferences preferences) {
1.300 + String id = NbBundle.getMessage(ModernizeCodeAudit.class, "LBL_ProviderName"); // NOI18N
1.301 + String description = NbBundle.getMessage(ModernizeCodeAudit.class, "LBL_ProviderDescription"); // NOI18N
1.302 + return new ModernizeCodeAudit(id, id, description, "error", false, preferences); // NOI18N
1.303 + }
1.304 + }
1.305 +
1.306 + private String oldPath;
1.307 +
1.308 + @Override
1.309 + public synchronized Collection<CodeAudit> getAudits() {
1.310 + String path = ClangAnalyzerOptions.getClangAnalyzerPath();
1.311 +
1.312 + if (path == null) {
1.313 + return Collections.emptyList();
1.314 + }
1.315 +
1.316 + if (oldPath == null) {
1.317 + oldPath = path;
1.318 + }
1.319 +
1.320 + if (audits == null || !oldPath.equals(path)) {
1.321 + List<CodeAudit> res = DiagnosticsTool.getAudits(path, ExecutionEnvironmentFactory.getLocal(), AnalyzerPreferences.getAuditPreferences());
1.322 +
1.323 + audits = res;
1.324 + oldPath = path;
1.325 + }
1.326 + return audits;
1.327 + }
1.328 +
1.329 + public Collection<String> getEnabledChecks() {
1.330 + Collection<CodeAudit> auditList = getAudits();
1.331 + List<String> enabled = new ArrayList<String>();
1.332 + for (CodeAudit codeAudit : auditList) {
1.333 + if (codeAudit.isEnabled()) {
1.334 + enabled.add(codeAudit.getID());
1.335 + }
1.336 + }
1.337 + return enabled.size() == auditList.size() ? Collections.singleton("*") : enabled; //NOI18N
1.338 + }
1.339 +
1.340 + @Override
1.341 + public AuditPreferences getPreferences() {
1.342 + return AnalyzerPreferences.getAuditPreferences();
1.343 + }
1.344 +
1.345 + @Override
1.346 + public JComponent createComponent(Preferences context) {
1.347 + return new JLabel();
1.348 + }
1.349 +
1.350 + public static interface ErrorInfoWithId {
1.351 +
1.352 + String getId();
1.353 + }
1.354 +
1.355 + public static final class FatalErrorInfo implements CsmErrorInfo, ErrorInfoWithId {
1.356 +
1.357 + private final String id;
1.358 + private final String message;
1.359 +
1.360 + public FatalErrorInfo(String id, String message) {
1.361 + this.id = id;
1.362 + this.message = message;
1.363 + }
1.364 +
1.365 + @Override
1.366 + public String getMessage() {
1.367 + return message;
1.368 + }
1.369 +
1.370 + @Override
1.371 + public Severity getSeverity() {
1.372 + return Severity.WARNING;
1.373 + }
1.374 +
1.375 + @Override
1.376 + public int getStartOffset() {
1.377 + return 0;
1.378 + }
1.379 +
1.380 + @Override
1.381 + public int getEndOffset() {
1.382 + return 1;
1.383 + }
1.384 +
1.385 + @Override
1.386 + public String getId() {
1.387 + return id;
1.388 + }
1.389 + }
1.390 +
1.391 + @ServiceProvider(service = CsmErrorInfoHintProvider.class, position = 9100)
1.392 + public final static class ModerinzeHintProvider extends CsmErrorInfoHintProvider {
1.393 +
1.394 + @Override
1.395 + protected List<Fix> doGetFixes(CsmErrorInfo info, List<Fix> alreadyFound) {
1.396 + if (info instanceof ModernizeErrorInfo) {
1.397 + alreadyFound.add(new ConfigureHintsFix((ModernizeErrorInfo) info));
1.398 + }
1.399 + return alreadyFound;
1.400 + }
1.401 + }
1.402 +
1.403 + @ServiceProvider(service = CsmErrorInfoHintProvider.class, position = 1600)
1.404 + public static final class ModernizeFixProvider extends CsmErrorInfoHintProvider {
1.405 +
1.406 + @Override
1.407 + protected List<Fix> doGetFixes(CsmErrorInfo info, List<Fix> alreadyFound) {
1.408 + alreadyFound.addAll(createFixes(info));
1.409 + return alreadyFound;
1.410 + }
1.411 + }
1.412 +
1.413 + private static List<? extends Fix> createFixes(CsmErrorInfo info) {
1.414 + if (info instanceof ModernizeErrorInfo) {
1.415 + ModernizeErrorInfo mei = (ModernizeErrorInfo) info;
1.416 + List<Replacement> replacements = mei.getDiagnostics().getReplacements();
1.417 + if (!replacements.isEmpty()) {
1.418 + return Collections.singletonList(new ModernizeFix(replacements, mei.getId()));
1.419 + }
1.420 + }
1.421 + return Collections.EMPTY_LIST;
1.422 + }
1.423 +}