2 // MSBuildErrorParser.cs: Parser for MSBuild-format error messages.
5 // Michael Hutchinson (m.j.hutchinson@gmail.com)
7 // Copyright 2014 Xamarin Inc. (http://www.xamarin.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 namespace Microsoft.Build.Utilities
32 static class MSBuildErrorParser
36 public string Origin { get; set; }
37 public int Line { get; set; }
38 public int Column { get; set; }
39 public int EndLine { get; set; }
40 public int EndColumn { get; set; }
41 public string Subcategory { get; set; }
42 public bool IsError { get; set; }
43 public string Code { get; set; }
44 public string Message { get; set; }
47 // Parses single-line error message in the standard MSBuild error format:
49 // [origin[(position)]:][subcategory] category code: [message]
51 // Components in [] square brackets are optional.
52 // Components are as follows:
53 // origin: tool name or filename, may contain whitespace, no colons except the drive letter
54 // position: line/col position or range in the file, with one of the following forms:
55 // (l), (l,c), (l,c-c), (l,c,l,c)
56 // subcategory: arbitrary text, may contain whitepsace
57 // code: error code, no whietspace or punctuation
58 // message: arbitraty text, no restrictions
60 public static Result TryParseLine (string line)
62 int originEnd, originStart = 0;
63 var result = new Result ();
65 MoveNextNonSpace (line, ref originStart);
67 if (originStart >= line.Length)
70 //find the origin section
71 //the filename may include a colon for Windows drive e.g. C:\foo, so ignore colon in first 2 chars
72 if (line[originStart] != ':') {
73 if (originStart + 2 >= line.Length)
76 if ((originEnd = line.IndexOf (':', originStart + 2) - 1) < 0)
79 originEnd = originStart;
82 int categoryStart = originEnd + 2;
84 if (categoryStart >= line.Length)
87 MovePrevNonSpace (line, ref originEnd);
89 //if there is no origin section, then we can't parse the message
90 if (originEnd < 0 || originEnd < originStart)
93 //find the category section, if there is one
94 MoveNextNonSpace (line, ref categoryStart);
96 if (categoryStart >= line.Length)
99 int categoryEnd = line.IndexOf (':', categoryStart) - 1;
100 int messageStart = categoryEnd + 2;
102 if (categoryEnd >= 0) {
103 MovePrevNonSpace (line, ref categoryEnd);
104 if (categoryEnd <= categoryStart)
108 //if there is a category section and it parses
109 if (categoryEnd > 0 && ParseCategory (line, categoryStart, categoryEnd, result)) {
110 //then parse the origin section
111 if (originEnd > originStart && !ParseOrigin (line, originStart, originEnd, result))
114 //there is no origin, parse the origin section as if it were the category
115 if (!ParseCategory (line, originStart, originEnd, result))
117 messageStart = categoryStart;
120 //read the remaining message
121 MoveNextNonSpace (line, ref messageStart);
122 int messageEnd = line.Length - 1;
123 MovePrevNonSpace (line, ref messageEnd, messageStart);
124 if (messageEnd > messageStart) {
125 result.Message = line.Substring (messageStart, messageEnd - messageStart + 1);
133 // filename (line,col) | tool :
134 static bool ParseOrigin (string line, int start, int end, Result result)
137 if (line [end] != ')') {
138 result.Origin = line.Substring (start, end - start + 1);
142 //scan back for matching (, assuming at least one char between them
143 int posStart = line.LastIndexOf ('(', end - 2, end - start - 2);
147 if (!ParsePosition (line, posStart + 1, end, result)) {
148 result.Origin = line.Substring (start, end - start + 1);
153 MovePrevNonSpace (line, ref end, start);
155 result.Origin = line.Substring (start, end - start + 1);
159 static bool ParseLineColVal (string str, out int val)
162 val = int.Parse (str);
164 } catch (OverflowException) {
167 } catch (FormatException) {
181 // Unexpected patterns of commas/dashes abort parsing, discarding all values.
182 // Any other characters abort parsing and the (...) gets treated as pert of the filename.
183 // Overflows are silently treated as zeroes.
185 static bool ParsePosition (string str, int start, int end, Result result)
187 int line = 0, col = 0, endLine = 0, endCol = 0;
189 var a = str.Substring (start, end - start).Split (',');
191 if (a.Length > 4 || a.Length == 3)
196 ParseLineColVal (a [0], out line) &&
197 ParseLineColVal (a [1], out col) &&
198 ParseLineColVal (a [2], out endLine) &&
199 ParseLineColVal (a [3], out endCol);
203 var b = a [0].Split ('-');
206 if (!ParseLineColVal (b [0], out line))
211 if (!ParseLineColVal (b [1], out endLine))
215 var c = a [1].Split ('-');
218 if (!ParseLineColVal (c [0], out col))
221 if (!ParseLineColVal (c [1], out endCol))
229 result.EndLine = endLine;
230 result.EndColumn = endCol;
234 static bool ParseCategory (string line, int start, int end, Result result)
237 MovePrevWordStart (line, ref idx, start);
241 string code = line.Substring (idx, end - idx + 1);
244 MovePrevNonSpace (line, ref idx, start);
246 MovePrevWordStart (line, ref idx, start);
250 string category = line.Substring (idx , end - idx + 1);
251 if (string.Equals (category, "error", StringComparison.OrdinalIgnoreCase))
252 result.IsError = true;
253 else if (!string.Equals (category, "warning", StringComparison.OrdinalIgnoreCase))
260 MovePrevNonSpace (line, ref idx, start);
261 result.Subcategory = line.Substring (start, idx - start + 1);
263 result.Subcategory = "";
269 static void MoveNextNonSpace (string s, ref int idx)
271 while (idx < s.Length && char.IsWhiteSpace (s[idx]))
275 static void MovePrevNonSpace (string s, ref int idx, int min = 0)
277 while (idx > min && char.IsWhiteSpace (s[idx]))
281 static void MovePrevWordStart (string s, ref int idx, int min = 0)
283 while (idx > min && char.IsLetterOrDigit (s[idx - 1]))