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