blob: db5b055c7d4b870ed88a4be97aebd38cc251c1e1 [file] [log] [blame]
Zachary Turner1122be82016-09-07 18:28:55 +00001using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Text;
6using System.Threading.Tasks;
7using YamlDotNet.Serialization;
8using YamlDotNet.Serialization.NamingConventions;
9
10namespace 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}