Zachary Turner | 1122be8 | 2016-09-07 18:28:55 +0000 | [diff] [blame] | 1 | using System;
|
| 2 | using System.Collections.Generic;
|
| 3 | using System.IO;
|
| 4 | using System.Linq;
|
| 5 | using System.Text;
|
| 6 | using System.Threading.Tasks;
|
| 7 | using YamlDotNet.Serialization;
|
| 8 | using YamlDotNet.Serialization.NamingConventions;
|
| 9 |
|
| 10 | namespace LLVM.ClangTidy
|
| 11 | {
|
| 12 | static class ClangTidyConfigParser
|
| 13 | {
|
| 14 | public class CheckOption
|
| 15 | {
|
| 16 | [YamlAlias("key")]
|
| 17 | public string Key { get; set; }
|
| 18 |
|
| 19 | [YamlAlias("value")]
|
| 20 | public string Value { get; set; }
|
| 21 | }
|
| 22 | public class ClangTidyYaml
|
| 23 | {
|
| 24 | [YamlAlias("Checks")]
|
| 25 | public string Checks { get; set; }
|
| 26 |
|
| 27 | [YamlAlias("CheckOptions")]
|
| 28 | public List<CheckOption> CheckOptions { get; set; }
|
| 29 | }
|
| 30 |
|
| 31 | public static List<KeyValuePair<string, ClangTidyProperties>> ParseConfigurationChain(string ClangTidyFile)
|
| 32 | {
|
| 33 | List<KeyValuePair<string, ClangTidyProperties>> Result = new List<KeyValuePair<string, ClangTidyProperties>>();
|
| 34 | Result.Add(new KeyValuePair<string, ClangTidyProperties>(null, ClangTidyProperties.RootProperties));
|
| 35 |
|
| 36 | foreach (string P in Utility.SplitPath(ClangTidyFile).Reverse())
|
| 37 | {
|
| 38 | if (!Utility.HasClangTidyFile(P))
|
| 39 | continue;
|
| 40 |
|
| 41 | string ConfigFile = Path.Combine(P, ".clang-tidy");
|
| 42 |
|
| 43 | using (StreamReader Reader = new StreamReader(ConfigFile))
|
| 44 | {
|
| 45 | Deserializer D = new Deserializer(namingConvention: new PascalCaseNamingConvention());
|
| 46 | ClangTidyYaml Y = D.Deserialize<ClangTidyYaml>(Reader);
|
| 47 | ClangTidyProperties Parent = Result[Result.Count - 1].Value;
|
| 48 | ClangTidyProperties NewProps = new ClangTidyProperties(Parent);
|
| 49 | SetPropertiesFromYaml(Y, NewProps);
|
| 50 | Result.Add(new KeyValuePair<string, ClangTidyProperties>(P, NewProps));
|
| 51 | }
|
| 52 | }
|
| 53 | return Result;
|
| 54 | }
|
| 55 |
|
| 56 | enum TreeLevelOp
|
| 57 | {
|
| 58 | Enable,
|
| 59 | Disable,
|
| 60 | Inherit
|
| 61 | }
|
| 62 |
|
| 63 | public static void SerializeClangTidyFile(ClangTidyProperties Props, string ClangTidyFilePath)
|
| 64 | {
|
| 65 | List<string> CommandList = new List<string>();
|
| 66 | SerializeCheckTree(CommandList, Props.GetCheckTree(), TreeLevelOp.Inherit);
|
| 67 |
|
| 68 | CommandList.Sort((x, y) =>
|
| 69 | {
|
| 70 | bool LeftSub = x.StartsWith("-");
|
| 71 | bool RightSub = y.StartsWith("-");
|
| 72 | if (LeftSub && !RightSub)
|
| 73 | return -1;
|
| 74 | if (RightSub && !LeftSub)
|
| 75 | return 1;
|
| 76 | return StringComparer.CurrentCulture.Compare(x, y);
|
| 77 | });
|
| 78 |
|
| 79 | string ConfigFile = Path.Combine(ClangTidyFilePath, ".clang-tidy");
|
| 80 | using (StreamWriter Writer = new StreamWriter(ConfigFile))
|
| 81 | {
|
| 82 | Serializer S = new Serializer(namingConvention: new PascalCaseNamingConvention());
|
| 83 | ClangTidyYaml Yaml = new ClangTidyYaml();
|
| 84 | Yaml.Checks = String.Join(",", CommandList.ToArray());
|
| 85 | S.Serialize(Writer, Yaml);
|
| 86 | }
|
| 87 | }
|
| 88 |
|
| 89 | /// <summary>
|
| 90 | /// Convert the given check tree into serialized list of commands that can be written to
|
| 91 | /// the Yaml. The goal here is to determine the minimal sequence of check commands that
|
| 92 | /// will produce the exact configuration displayed in the UI. This is complicated by the
|
| 93 | /// fact that an inherited True is not the same as an explicitly specified True. If the
|
| 94 | /// user has chosen to inherit a setting in a .clang-tidy file, then changing it in the
|
| 95 | /// parent should show the reflected changes in the current file as well. So we cannot
|
| 96 | /// simply -* everything and then add in the checks we need, because -* immediately marks
|
| 97 | /// every single check as explicitly false, thus disabling inheritance.
|
| 98 | /// </summary>
|
| 99 | /// <param name="CommandList">State passed through this recursive algorithm representing
|
| 100 | /// the sequence of commands we have determined so far.
|
| 101 | /// </param>
|
| 102 | /// <param name="Tree">The check tree to serialize. This is the parameter that will be
|
| 103 | /// recursed on as successive subtrees get serialized to `CommandList`.
|
| 104 | /// </param>
|
| 105 | /// <param name="CurrentOp">The current state of the subtree. For example, if the
|
| 106 | /// algorithm decides to -* an entire subtree and then add back one single check,
|
| 107 | /// after adding a -subtree-* command to CommandList, it would pass in a value of
|
| 108 | /// CurrentOp=TreeLevelOp.Disable when it recurses down. This allows deeper iterations
|
| 109 | /// of the algorithm to know what kind of command (if any) needs to be added to CommandList
|
| 110 | /// in order to put a particular check into a particular state.
|
| 111 | /// </param>
|
| 112 | private static void SerializeCheckTree(List<string> CommandList, CheckTree Tree, TreeLevelOp CurrentOp)
|
| 113 | {
|
| 114 | int NumChecks = Tree.CountChecks;
|
| 115 | int NumDisabled = Tree.CountExplicitlyDisabledChecks;
|
| 116 | int NumEnabled = Tree.CountExplicitlyEnabledChecks;
|
| 117 | int NumInherited = Tree.CountInheritedChecks;
|
| 118 |
|
| 119 | if (NumChecks == 0)
|
| 120 | return;
|
| 121 |
|
| 122 | if (NumInherited > 0)
|
| 123 | System.Diagnostics.Debug.Assert(CurrentOp == TreeLevelOp.Inherit);
|
| 124 |
|
| 125 | // If this entire tree is inherited, just exit, nothing about this needs to
|
| 126 | // go in the clang-tidy file.
|
| 127 | if (NumInherited == NumChecks)
|
| 128 | return;
|
| 129 |
|
| 130 | TreeLevelOp NewOp = CurrentOp;
|
| 131 | // If there are no inherited properties in this subtree, decide whether to
|
| 132 | // explicitly enable or disable this subtree. Decide by looking at whether
|
| 133 | // there is a larger proportion of disabled or enabled descendants. If
|
| 134 | // there are more disabled items in this subtree for example, disabling the
|
| 135 | // subtree will lead to a smaller configuration file.
|
| 136 | if (NumInherited == 0)
|
| 137 | {
|
| 138 | if (NumDisabled >= NumEnabled)
|
| 139 | NewOp = TreeLevelOp.Disable;
|
| 140 | else
|
| 141 | NewOp = TreeLevelOp.Enable;
|
| 142 | }
|
| 143 |
|
| 144 | if (NewOp == TreeLevelOp.Disable)
|
| 145 | {
|
| 146 | // Only add an explicit disable command if the tree was not already disabled
|
| 147 | // to begin with.
|
| 148 | if (CurrentOp != TreeLevelOp.Disable)
|
| 149 | {
|
| 150 | string WildcardPath = "*";
|
| 151 | if (Tree.Path != null)
|
| 152 | WildcardPath = Tree.Path + "-" + WildcardPath;
|
| 153 | CommandList.Add("-" + WildcardPath);
|
| 154 | }
|
| 155 | // If the entire subtree was disabled, there's no point descending.
|
| 156 | if (NumDisabled == NumChecks)
|
| 157 | return;
|
| 158 | }
|
| 159 | else if (NewOp == TreeLevelOp.Enable)
|
| 160 | {
|
| 161 | // Only add an explicit enable command if the tree was not already enabled
|
| 162 | // to begin with. Note that if we're at the root, all checks are already
|
| 163 | // enabled by default, so there's no need to explicitly include *
|
| 164 | if (CurrentOp != TreeLevelOp.Enable && Tree.Path != null)
|
| 165 | {
|
| 166 | string WildcardPath = Tree.Path + "-*";
|
| 167 | CommandList.Add(WildcardPath);
|
| 168 | }
|
| 169 | // If the entire subtree was enabled, there's no point descending.
|
| 170 | if (NumEnabled == NumChecks)
|
| 171 | return;
|
| 172 | }
|
| 173 |
|
| 174 | foreach (var Child in Tree.Children)
|
| 175 | {
|
| 176 | if (Child.Value is CheckLeaf)
|
| 177 | {
|
| 178 | CheckLeaf Leaf = (CheckLeaf)Child.Value;
|
| 179 | if (Leaf.CountExplicitlyEnabledChecks == 1 && NewOp != TreeLevelOp.Enable)
|
| 180 | CommandList.Add(Leaf.Path);
|
| 181 | else if (Leaf.CountExplicitlyDisabledChecks == 1 && NewOp != TreeLevelOp.Disable)
|
| 182 | CommandList.Add("-" + Leaf.Path);
|
| 183 | continue;
|
| 184 | }
|
| 185 |
|
| 186 | System.Diagnostics.Debug.Assert(Child.Value is CheckTree);
|
| 187 | CheckTree ChildTree = (CheckTree)Child.Value;
|
| 188 | SerializeCheckTree(CommandList, ChildTree, NewOp);
|
| 189 | }
|
| 190 | }
|
| 191 |
|
| 192 | private static void SetPropertiesFromYaml(ClangTidyYaml Yaml, ClangTidyProperties Props)
|
| 193 | {
|
| 194 | string[] CheckCommands = Yaml.Checks.Split(',');
|
| 195 | foreach (string Command in CheckCommands)
|
| 196 | {
|
| 197 | if (Command == null || Command.Length == 0)
|
| 198 | continue;
|
| 199 | bool Add = true;
|
| 200 | string Pattern = Command;
|
| 201 | if (Pattern[0] == '-')
|
| 202 | {
|
| 203 | Pattern = Pattern.Substring(1);
|
| 204 | Add = false;
|
| 205 | }
|
| 206 |
|
| 207 | foreach (var Match in CheckDatabase.Checks.Where(x => Utility.MatchWildcardString(x.Name, Pattern)))
|
| 208 | {
|
| 209 | Props.SetDynamicValue(Match.Name, Add);
|
| 210 | }
|
| 211 | }
|
| 212 | }
|
| 213 | }
|
| 214 | }
|