| 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); | |
| } | |
| } | |
| } | |
| } | |
| } |