c.s.tools.ide.analysis.modernize/src/com/sun/tools/ide/analysis/modernize/impl/YamlParser.java
author Ilia Gromov <ilia@netbeans.org>
Wed, 07 Jun 2017 20:23:29 +0300
branchrelease82
changeset 18423 b9d9af239a0c
permissions -rw-r--r--
Fixing #270763 - Move clang-tidy integration to nb contrib
* * *
Fixing #270763 - Move clang-tidy integration to nb contrib - move wrapper
* * *
Fixing #270763 - Move clang-tidy integration to nb contrib - sign nbm
* * *
Fixing #270763 - Move clang-tidy integration to nb contrib - move tests
* * *
Fixing #270763 - Move clang-tidy integration to nb contrib - data for a new test
* * *
Fixed #270839 - [clang-tidy] Group checks in Editor hints
(transplanted from 35b6125ef00c470655dac6673075f5c12ec74593)
     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 static com.sun.tools.ide.analysis.modernize.impl.YamlParser.Diagnostics.UNDEFINED;
    43 import java.util.ArrayList;
    44 import java.util.Collections;
    45 import java.util.List;
    46 import java.util.StringTokenizer;
    47 import java.util.logging.Level;
    48 
    49 /**
    50  *
    51  * @author Ilia Gromov
    52  */
    53 public class YamlParser {
    54     // TODO check for no-replacements case
    55 
    56     private static final YamlParser INSTANCE = new YamlParser();
    57 
    58     public static YamlParser getDefault() {
    59         return INSTANCE;
    60     }
    61 
    62     private static final String OPEN_TAG = "---"; //NOI18N
    63     private static final String CLOSE_TAG = "..."; //NOI18N
    64 
    65     private static final String MAIN_SOURCE_FILE = "MainSourceFile"; //NOI18N
    66     private static final String CHECK_NAME = "CheckName"; //NOI18N
    67     private static final String LEVEL = "Level"; //NOI18N
    68     private static final String MESSAGE_FILE_PATH = "MessageFilePath"; //NOI18N
    69     private static final String MESSAGE_FILE_OFFSET = "MessageFileOffset"; //NOI18N
    70     private static final String MESSAGE = "Message"; //NOI18N
    71     private static final String REPLACEMENTS = "Replacements"; //NOI18N
    72     private static final String DIAGNOSTICS = "Diagnostics"; //NOI18N
    73 
    74     private static final String FILE_PATH = "FilePath"; //NOI18N
    75     private static final String OFFSET = "Offset"; //NOI18N
    76     private static final String LENGTH = "Length"; //NOI18N
    77     private static final String REPLACEMENT_TEXT = "ReplacementText"; //NOI18N
    78 
    79     // clang-modernize:
    80 // ---
    81 // MainSourceFile:  /home/ilia/NetBeansProjects/CppApplication_46/main.cpp
    82 // Replacements:    
    83 //   - FilePath:        /home/ilia/NetBeansProjects/CppApplication_46/newfile.h
    84 //     Offset:          101
    85 //     Length:          1
    86 //     ReplacementText: nullptr
    87 //   - FilePath:        /home/ilia/NetBeansProjects/CppApplication_46/newfile1.h
    88 //     Offset:          380
    89 //     Length:          1
    90 //     ReplacementText: nullptr
    91 //   - FilePath:        /home/ilia/NetBeansProjects/CppApplication_46/main.cpp
    92 //     Offset:          152
    93 //     Length:          1
    94 //     ReplacementText: nullptr
    95 // ...
    96     // clang-tidy:
    97 // ---
    98 // MainSourceFile:  ''
    99 // Diagnostics:     
   100 //   CheckName:       misc-macro-parentheses
   101 //   Replacements:    
   102 //     - FilePath:        /media/SSD_/code/zdoom/main_1.cpp
   103 //       Offset:          1352
   104 //       Length:          0
   105 //       ReplacementText: '('
   106 //     - FilePath:        /media/SSD_/code/zdoom/main_1.cpp
   107 //       Offset:          1353
   108 //       Length:          0
   109 //       ReplacementText: ')'
   110 // Diagnostics:     
   111 //   CheckName:       misc-macro-parentheses
   112 //   Replacements:    
   113 //     - FilePath:        /media/SSD_/code/zdoom/main_1.cpp
   114 //       Offset:          1446
   115 //       Length:          0
   116 //       ReplacementText: '('
   117 //     - FilePath:        /media/SSD_/code/zdoom/main_1.cpp
   118 //       Offset:          1447
   119 //       Length:          0
   120 //       ReplacementText: ')'
   121 // ...
   122     public List<Diagnostics> parseYaml(String output) {
   123         try {
   124             if (output.isEmpty()) {
   125                 // No warnings in header = no 'Main Source File' element
   126                 return Collections.EMPTY_LIST;
   127             }
   128             StringTokenizer st = new StringTokenizer(output, "\n"); //NOI18N
   129             String line;
   130 
   131             // ---
   132             line = st.nextToken();
   133 
   134             line = st.nextToken();
   135             String mainSourceFile = getValue(MAIN_SOURCE_FILE, line);
   136 
   137             // Replacements:    
   138             line = st.nextToken();
   139             ClangTidyVersion version = line.contains(REPLACEMENTS)
   140                     ? ClangTidyVersion.OLD_FORMAT
   141                     : ClangTidyVersion.NEW_FORMAT;
   142 
   143             List<Diagnostics> list = version.parser.parse(st);
   144 
   145             return list;
   146         } catch (Exception ex) {
   147             ModernizeErrorProvider.LOG.log(Level.INFO, "Can't parse output line");
   148             return Collections.EMPTY_LIST;
   149         }
   150     }
   151 
   152     private static List<Replacement> parseReplacements(StringTokenizer st) {
   153         List<Replacement> replacements = new ArrayList<Replacement>();
   154         String line;
   155 
   156         // eat Replacements:
   157         line = st.nextToken();
   158 
   159         if (!line.contains(REPLACEMENTS)) {
   160             return replacements;
   161         }
   162 
   163         while (st.hasMoreTokens()) {
   164             line = st.nextToken();
   165 
   166             if (line.equals(CLOSE_TAG) || !line.contains(FILE_PATH)) {
   167                 break;
   168             }
   169 
   170             String filePath = getValue(FILE_PATH, line);
   171 
   172             line = st.nextToken();
   173             int offset = Integer.parseInt(getValue(OFFSET, line));
   174 
   175             line = st.nextToken();
   176             int length = Integer.parseInt(getValue(LENGTH, line));
   177 
   178             line = st.nextToken();
   179             String replacementText = getValue(REPLACEMENT_TEXT, line);
   180             while (replacementText == null && st.hasMoreElements()) {
   181                 line = line + "\n" + st.nextElement(); //NOI18N
   182                 replacementText = getValue(REPLACEMENT_TEXT, line);
   183             }
   184 
   185             Replacement replacement = new Replacement(filePath, offset, length, replacementText);
   186             replacements.add(replacement);
   187 
   188         }
   189         return replacements;
   190     }
   191 
   192     private static String getValue(String key, String line) {
   193         int keyStart = line.indexOf(key);
   194         assert keyStart != -1;
   195         int keyEnd = keyStart + key.length() + 1;
   196         String trimmed = line.substring(keyEnd).trim();
   197         boolean isTextValue = trimmed.charAt(0) == '\'';
   198         if (isTextValue) {
   199             if (trimmed.length() == 1) {
   200                 // '
   201                 // abcd'
   202                 return null;
   203             } else if (trimmed.charAt(trimmed.length() - 1) == '\'') {
   204                 trimmed = trimmed.substring(1, trimmed.length() - 1);
   205             } else {
   206                 return null;
   207             }
   208         }
   209         return trimmed;
   210     }
   211 
   212     public static class TranslationUnitDiagnostics {
   213 
   214         private final String mainSourceFilePath;
   215         private final String context;
   216         private final List<Diagnostics> diags;
   217 
   218         public TranslationUnitDiagnostics(String mainSourceFilePath, String context, List<Diagnostics> diags) {
   219             this.mainSourceFilePath = mainSourceFilePath;
   220             this.context = context;
   221             this.diags = diags;
   222         }
   223 
   224         public String getMainSourceFilePath() {
   225             return mainSourceFilePath;
   226         }
   227 
   228         public String getContext() {
   229             return context;
   230         }
   231 
   232         public List<Diagnostics> getDiags() {
   233             return diags;
   234         }
   235     }
   236 
   237     public static class Diagnostics {
   238 
   239         public static final String UNDEFINED = "undefined"; // NOI18N
   240 
   241         public enum Level {
   242             warning, error
   243         }
   244 
   245         private final String checkName;
   246         private final Diagnostics.Level level;
   247         private final String messageFilePath;
   248         private final int messageFileOffset;
   249         private final String message;
   250         private final List<Replacement> replacements;
   251 
   252         public Diagnostics(String checkName, Diagnostics.Level level, String messageFilePath, int messageFileOffset, String message, List<Replacement> replacements) {
   253             this.checkName = checkName;
   254             this.level = level;
   255             this.messageFilePath = messageFilePath;
   256             this.messageFileOffset = messageFileOffset;
   257             this.message = message;
   258             this.replacements = replacements;
   259         }
   260 
   261         public String getCheckName() {
   262             return checkName;
   263         }
   264 
   265         public Level getLevel() {
   266             return level;
   267         }
   268 
   269         public String getMessageFilePath() {
   270             return messageFilePath;
   271         }
   272 
   273         public int getMessageFileOffset() {
   274             return messageFileOffset;
   275         }
   276 
   277         public String getMessage() {
   278             return message;
   279         }
   280 
   281         public List<Replacement> getReplacements() {
   282             return replacements;
   283         }
   284     }
   285 
   286     public static class Replacement {
   287 
   288         public final String filePath;
   289         public final int offset;
   290         public final int length;
   291         public final String replacementText;
   292 
   293         public Replacement(String FilePath, int offset, int length, String replacementText) {
   294             this.filePath = FilePath;
   295             this.offset = offset;
   296             this.length = length;
   297             this.replacementText = replacementText;
   298         }
   299     }
   300 
   301     private static enum ClangTidyVersion {
   302         NEW_FORMAT(new NewFormatParser()),
   303         OLD_FORMAT(new OldFormatParser());
   304 
   305         public final TidyParser parser;
   306 
   307         private ClangTidyVersion(TidyParser parser) {
   308             this.parser = parser;
   309         }
   310     }
   311 
   312     private static abstract class TidyParser {
   313 
   314         abstract List<Diagnostics> parse(StringTokenizer st);
   315     }
   316 
   317     private static class NewFormatParser extends TidyParser {
   318 
   319         @Override
   320         public List<Diagnostics> parse(StringTokenizer st) {
   321             List<Diagnostics> list = new ArrayList<Diagnostics>();
   322 
   323             String line;
   324             while (st.hasMoreElements()) {
   325                 line = st.nextToken();
   326                 String checkName = getValue(CHECK_NAME, line);
   327 
   328                 line = st.nextToken();
   329                 Diagnostics.Level level = Diagnostics.Level.valueOf(getValue(LEVEL, line).toLowerCase());
   330 
   331                 line = st.nextToken();
   332                 String messageFilePath = getValue(MESSAGE_FILE_PATH, line);
   333 
   334                 line = st.nextToken();
   335                 int messageFileOffset = Integer.parseInt(getValue(MESSAGE_FILE_OFFSET, line));
   336 
   337                 line = st.nextToken();
   338                 String message = getValue(MESSAGE, line);
   339 
   340                 List<Replacement> replacements = parseReplacements(st);
   341 
   342                 list.add(new Diagnostics(checkName, level, messageFilePath, messageFileOffset, message, replacements));
   343             }
   344             return list;
   345         }
   346 
   347     }
   348 
   349     private static class OldFormatParser extends TidyParser {
   350 
   351         @Override
   352         public List<Diagnostics> parse(StringTokenizer st) {
   353             List<Replacement> parseReplacements = parseReplacements(st);
   354             return Collections.singletonList(new Diagnostics(UNDEFINED, Diagnostics.Level.warning, "", -1, "", parseReplacements)); //NOI18N
   355         }
   356     }
   357 }