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