forked from OSchip/llvm-project
215 lines
9.3 KiB
C#
215 lines
9.3 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|