aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-10-25 07:01:26 -0700
committerRob Mensching <rob@firegiant.com>2022-10-31 10:57:14 -0700
commit217182f85fa737d20fb5846f395cabfa599bf1c6 (patch)
tree5930f91922aa79f34d3add6ab0a78b236057f079 /src
parent2cb28477721baaadba96a01c0763d5361d7a3d94 (diff)
downloadwix-217182f85fa737d20fb5846f395cabfa599bf1c6.tar.gz
wix-217182f85fa737d20fb5846f395cabfa599bf1c6.tar.bz2
wix-217182f85fa737d20fb5846f395cabfa599bf1c6.zip
Update converted source line numbers when conversion is saved
The conversion process has a good chance of changing the shape of .wxs source file. Therefore, the old line numbers may be wrong and should be replaced with the line numbers from the converted file when it is saved. Fixes 6969
Diffstat (limited to 'src')
-rw-r--r--src/wix/WixToolset.Converters/HashSetExtensions.cs17
-rw-r--r--src/wix/WixToolset.Converters/WixConverter.cs263
-rw-r--r--src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs38
-rw-r--r--src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs11
-rw-r--r--src/wix/test/WixToolsetTest.Converters/TestData/FixDeclarationAndNamespace/FixDeclarationAndNamespace.wxs4
5 files changed, 272 insertions, 61 deletions
diff --git a/src/wix/WixToolset.Converters/HashSetExtensions.cs b/src/wix/WixToolset.Converters/HashSetExtensions.cs
new file mode 100644
index 00000000..49b0d4ac
--- /dev/null
+++ b/src/wix/WixToolset.Converters/HashSetExtensions.cs
@@ -0,0 +1,17 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Converters
4{
5 using System.Collections.Generic;
6
7 internal static class HashSetExtensions
8 {
9 public static void AddRange<T>(this HashSet<T> set, IEnumerable<T> values)
10 {
11 foreach (var value in values)
12 {
13 set.Add(value);
14 }
15 }
16 }
17}
diff --git a/src/wix/WixToolset.Converters/WixConverter.cs b/src/wix/WixToolset.Converters/WixConverter.cs
index de81c876..cf77681c 100644
--- a/src/wix/WixToolset.Converters/WixConverter.cs
+++ b/src/wix/WixToolset.Converters/WixConverter.cs
@@ -11,6 +11,7 @@ namespace WixToolset.Converters
11 using System.Text.RegularExpressions; 11 using System.Text.RegularExpressions;
12 using System.Xml; 12 using System.Xml;
13 using System.Xml.Linq; 13 using System.Xml.Linq;
14 using System.Xml.XPath;
14 using WixToolset.Data; 15 using WixToolset.Data;
15 using WixToolset.Data.WindowsInstaller; 16 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Extensibility.Services; 17 using WixToolset.Extensibility.Services;
@@ -272,6 +273,8 @@ namespace WixToolset.Converters
272 { WixConverter.WixLocalizationElementWithoutNamespaceName, this.ConvertWixLocalizationElementWithoutNamespace }, 273 { WixConverter.WixLocalizationElementWithoutNamespaceName, this.ConvertWixLocalizationElementWithoutNamespace },
273 }; 274 };
274 275
276 this.ConversionMessages = new List<Message>();
277
275 this.Messaging = messaging; 278 this.Messaging = messaging;
276 279
277 this.IndentationAmount = indentationAmount; 280 this.IndentationAmount = indentationAmount;
@@ -285,7 +288,7 @@ namespace WixToolset.Converters
285 288
286 private CustomTableTarget CustomTableSetting { get; } 289 private CustomTableTarget CustomTableSetting { get; }
287 290
288 private int Messages { get; set; } 291 private List<Message> ConversionMessages { get; }
289 292
290 private HashSet<ConverterTestType> ErrorsAsWarnings { get; set; } 293 private HashSet<ConverterTestType> ErrorsAsWarnings { get; set; }
291 294
@@ -308,54 +311,35 @@ namespace WixToolset.Converters
308 /// </summary> 311 /// </summary>
309 /// <param name="sourceFile">The file to convert.</param> 312 /// <param name="sourceFile">The file to convert.</param>
310 /// <param name="saveConvertedFile">Option to save the converted Messages that are found.</param> 313 /// <param name="saveConvertedFile">Option to save the converted Messages that are found.</param>
311 /// <returns>The number of Messages found.</returns> 314 /// <returns>The number of conversions found.</returns>
312 public int ConvertFile(string sourceFile, bool saveConvertedFile) 315 public int ConvertFile(string sourceFile, bool saveConvertedFile)
313 { 316 {
314 var document = this.OpenSourceFile(sourceFile); 317 var savedDocument = false;
315 318
316 if (document is null) 319 if (this.TryOpenSourceFile(sourceFile, out var document))
317 { 320 {
318 return 1; 321 this.Convert(document);
319 }
320
321 this.ConvertDocument(document);
322 322
323 // Fix Messages if requested and necessary. 323 // Fix Messages if requested and necessary.
324 if (saveConvertedFile && 0 < this.Messages) 324 if (saveConvertedFile && 0 < this.ConversionMessages.Count)
325 { 325 {
326 this.SaveDocument(document); 326 savedDocument = this.SaveDocument(document);
327 }
327 } 328 }
328 329
329 return this.Messages; 330 return this.ReportMessages(document, savedDocument);
330 } 331 }
331 332
332 /// <summary> 333 /// <summary>
333 /// Convert a document. 334 /// Convert a document.
334 /// </summary> 335 /// </summary>
335 /// <param name="document">The document to convert.</param> 336 /// <param name="document">The document to convert.</param>
336 /// <returns>The number of Messages found.</returns> 337 /// <returns>The number of conversions found.</returns>
337 public int ConvertDocument(XDocument document) 338 public int ConvertDocument(XDocument document)
338 { 339 {
339 // Reset the instance info. 340 this.Convert(document);
340 this.Messages = 0;
341 this.SourceVersion = 0;
342 this.Operation = ConvertOperation.Convert;
343
344 // Remove the declaration.
345 if (null != document.Declaration
346 && this.OnInformation(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line."))
347 {
348 document.Declaration = null;
349 TrimLeadingText(document);
350 }
351
352 this.XRoot = document.Root;
353
354 // Start converting the nodes at the top.
355 this.ConvertNodes(document.Nodes(), 0);
356 this.RemoveUnusedNamespaces(document.Root);
357 341
358 return this.Messages; 342 return this.ReportMessages(document, false);
359 } 343 }
360 344
361 /// <summary> 345 /// <summary>
@@ -366,22 +350,20 @@ namespace WixToolset.Converters
366 /// <returns>The number of Messages found.</returns> 350 /// <returns>The number of Messages found.</returns>
367 public int FormatFile(string sourceFile, bool saveConvertedFile) 351 public int FormatFile(string sourceFile, bool saveConvertedFile)
368 { 352 {
369 var document = this.OpenSourceFile(sourceFile); 353 var savedDocument = false;
370 354
371 if (document is null) 355 if (this.TryOpenSourceFile(sourceFile, out var document))
372 { 356 {
373 return 1; 357 this.FormatDocument(document);
374 }
375
376 this.FormatDocument(document);
377 358
378 // Fix Messages if requested and necessary. 359 // Fix Messages if requested and necessary.
379 if (saveConvertedFile && 0 < this.Messages) 360 if (saveConvertedFile && 0 < this.ConversionMessages.Count)
380 { 361 {
381 this.SaveDocument(document); 362 savedDocument = this.SaveDocument(document);
363 }
382 } 364 }
383 365
384 return this.Messages; 366 return this.ReportMessages(document, savedDocument);
385 } 367 }
386 368
387 /// <summary> 369 /// <summary>
@@ -391,43 +373,73 @@ namespace WixToolset.Converters
391 /// <returns>The number of Messages found.</returns> 373 /// <returns>The number of Messages found.</returns>
392 public int FormatDocument(XDocument document) 374 public int FormatDocument(XDocument document)
393 { 375 {
376 this.Format(document);
377
378 return this.ReportMessages(document, false);
379 }
380
381 private void Convert(XDocument document)
382 {
394 // Reset the instance info. 383 // Reset the instance info.
395 this.Messages = 0; 384 this.ConversionMessages.Clear();
396 this.SourceVersion = 0; 385 this.SourceVersion = 0;
397 this.Operation = ConvertOperation.Format; 386 this.Operation = ConvertOperation.Convert;
398 387
399 // Remove the declaration. 388 // Remove the declaration.
400 if (null != document.Declaration 389 if (null != document.Declaration
401 && this.OnInformation(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line.")) 390 && this.OnInformation(ConverterTestType.DeclarationPresent, document, "This file contains an XML declaration on the first line."))
402 { 391 {
403 document.Declaration = null; 392 document.Declaration = null;
404 TrimLeadingText(document); 393 TrimLeadingText(document);
405 } 394 }
406 395
396 this.XRoot = document.Root;
397
407 // Start converting the nodes at the top. 398 // Start converting the nodes at the top.
408 this.ConvertNodes(document.Nodes(), 0); 399 this.ConvertNodes(document.Nodes(), 0);
409 this.RemoveUnusedNamespaces(document.Root); 400 this.RemoveUnusedNamespaces(document.Root);
401 }
402
403 private void Format(XDocument document)
404 {
405 // Reset the instance info.
406 this.ConversionMessages.Clear();
407 this.SourceVersion = 0;
408 this.Operation = ConvertOperation.Format;
410 409
411 return this.Messages; 410 // Remove the declaration.
411 if (null != document.Declaration
412 && this.OnInformation(ConverterTestType.DeclarationPresent, document, "This file contains an XML declaration on the first line."))
413 {
414 document.Declaration = null;
415 TrimLeadingText(document);
416 }
417
418 this.XRoot = document.Root;
419
420 // Start converting the nodes at the top.
421 this.ConvertNodes(document.Nodes(), 0);
422 this.RemoveUnusedNamespaces(document.Root);
412 } 423 }
413 424
414 private XDocument OpenSourceFile(string sourceFile) 425 private bool TryOpenSourceFile(string sourceFile, out XDocument document)
415 { 426 {
416 this.SourceFile = sourceFile; 427 this.SourceFile = sourceFile;
417 428
418 try 429 try
419 { 430 {
420 return XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); 431 document = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
421 } 432 }
422 catch (XmlException e) 433 catch (XmlException e)
423 { 434 {
424 this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message); 435 this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message);
436 document = null;
425 } 437 }
426 438
427 return null; 439 return document != null;
428 } 440 }
429 441
430 private void SaveDocument(XDocument document) 442 private bool SaveDocument(XDocument document)
431 { 443 {
432 var ignoreDeclarationError = this.IgnoreErrors.Contains(ConverterTestType.DeclarationPresent); 444 var ignoreDeclarationError = this.IgnoreErrors.Contains(ConverterTestType.DeclarationPresent);
433 445
@@ -437,11 +449,15 @@ namespace WixToolset.Converters
437 { 449 {
438 document.Save(writer); 450 document.Save(writer);
439 } 451 }
452
453 return true;
440 } 454 }
441 catch (UnauthorizedAccessException) 455 catch (UnauthorizedAccessException)
442 { 456 {
443 this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file."); 457 this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file.");
444 } 458 }
459
460 return false;
445 } 461 }
446 462
447 private void ConvertNodes(IEnumerable<XNode> nodes, int level) 463 private void ConvertNodes(IEnumerable<XNode> nodes, int level)
@@ -2129,6 +2145,14 @@ namespace WixToolset.Converters
2129 convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value); 2145 convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value);
2130 } 2146 }
2131 2147
2148 if (convertedAttribute != attribute)
2149 {
2150 foreach (var message in attribute.Annotations<Message>())
2151 {
2152 convertedAttribute.AddAnnotation(message);
2153 }
2154 }
2155
2132 element.Add(convertedAttribute); 2156 element.Add(convertedAttribute);
2133 } 2157 }
2134 } 2158 }
@@ -2214,6 +2238,110 @@ namespace WixToolset.Converters
2214 } 2238 }
2215 } 2239 }
2216 2240
2241 private int ReportMessages(XDocument document, bool saved)
2242 {
2243 var conversionCount = this.ConversionMessages.Count;
2244
2245 // If the converted/formatted document was saved, update the source line numbers
2246 // in the messages since they will possibly be wrong.
2247 if (saved && conversionCount > 0)
2248 {
2249 var fixedupMessages = new HashSet<Message>();
2250
2251 // Load the converted document so we can look up new line numbers for
2252 // messages.
2253 var convertedDocument = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
2254 var convertedNavigator = convertedDocument.CreateNavigator();
2255
2256 // Look through all nodes in the document and try to fix up their line numbers.
2257 foreach (var elements in document.Descendants())
2258 {
2259 var fixedup = this.FixupMessageLineNumbers(elements, convertedNavigator);
2260 fixedupMessages.AddRange(fixedup);
2261
2262 // Attributes are not considered nodes so they must be enumerated independently.
2263 if (elements is XElement element)
2264 {
2265 foreach (var attribute in element.Attributes())
2266 {
2267 fixedup = this.FixupMessageLineNumbers(attribute, convertedNavigator);
2268 fixedupMessages.AddRange(fixedup);
2269 }
2270 }
2271 }
2272
2273 // For any messages that couldn't be fixed up, remove their line numbers since they point at lines
2274 // that are possibly wrong after the conversion.
2275 for (var i = 0; i < this.ConversionMessages.Count; ++i)
2276 {
2277 var message = this.ConversionMessages[i];
2278
2279 if (!fixedupMessages.Contains(message))
2280 {
2281 this.ConversionMessages[i] = new Message(new SourceLineNumber(this.SourceFile), message.Level, message.Id, message.ResourceNameOrFormat, message.MessageArgs);
2282 }
2283 }
2284 }
2285
2286 foreach (var message in this.ConversionMessages)
2287 {
2288 this.Messaging.Write(message);
2289 }
2290
2291 return conversionCount;
2292 }
2293
2294 private IReadOnlyCollection<Message> FixupMessageLineNumbers(XObject obj, XPathNavigator savedDocument)
2295 {
2296 var messages = obj.Annotations<Message>().ToList();
2297
2298 if (messages.Count == 0)
2299 {
2300 return Array.Empty<Message>();
2301 }
2302
2303 // We can't fix up line numbers based on attributes but we can fix up using their parent element, so do so.
2304 if (obj is XAttribute attribute)
2305 {
2306 obj = attribute.Parent;
2307 }
2308
2309 var fixedupMessages = new List<Message>();
2310
2311 var modifiedLineNumber = this.GetModifiedNodeLineNumber((XElement)obj, savedDocument);
2312
2313 foreach (var message in messages)
2314 {
2315 var fixedupMessage = message;
2316
2317 // If the line number wasn't modified, the existing message's source line nuber is correct
2318 // so skip creating a new one.
2319 if (modifiedLineNumber != null)
2320 {
2321 var index = this.ConversionMessages.IndexOf(message);
2322
2323 fixedupMessage = new Message(modifiedLineNumber, message.Level, message.Id, message.ResourceNameOrFormat, message.MessageArgs);
2324
2325 this.ConversionMessages[index] = fixedupMessage;
2326 }
2327
2328 fixedupMessages.Add(fixedupMessage);
2329 }
2330
2331 return fixedupMessages;
2332 }
2333
2334 private SourceLineNumber GetModifiedNodeLineNumber(XElement element, XPathNavigator savedDocument)
2335 {
2336 var xpathToOld = CalculateXPath(element);
2337
2338 var newNode = savedDocument.SelectSingleNode(xpathToOld);
2339 var newLineNumber = (newNode as IXmlLineInfo).LineNumber;
2340
2341 var oldLineNumber = (element as IXmlLineInfo)?.LineNumber;
2342 return (oldLineNumber != newLineNumber) ? new SourceLineNumber(this.SourceFile, newLineNumber) : null;
2343 }
2344
2217 /// <summary> 2345 /// <summary>
2218 /// Output an error message to the console. 2346 /// Output an error message to the console.
2219 /// </summary> 2347 /// </summary>
@@ -2250,10 +2378,8 @@ namespace WixToolset.Converters
2250 return false; 2378 return false;
2251 } 2379 }
2252 2380
2253 // Increase the message count. 2381 var sourceLine = SourceLineNumberForXmlLineInfo(this.SourceFile, node);
2254 this.Messages++;
2255 2382
2256 var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wix.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber);
2257 var prefix = String.Empty; 2383 var prefix = String.Empty;
2258 if (level == MessageLevel.Information) 2384 if (level == MessageLevel.Information)
2259 { 2385 {
@@ -2268,11 +2394,34 @@ namespace WixToolset.Converters
2268 2394
2269 var msg = new Message(sourceLine, level, (int)converterTestType, format, args); 2395 var msg = new Message(sourceLine, level, (int)converterTestType, format, args);
2270 2396
2271 this.Messaging.Write(msg); 2397 // Add the message as a node annotation so it could be possible to remap source line numbers
2398 // to their new locations after converting/formatting (since lines of code could be moved).
2399 node?.AddAnnotation(msg);
2400 this.ConversionMessages.Add(msg);
2272 2401
2273 return true; 2402 return true;
2274 } 2403 }
2275 2404
2405 private static string CalculateXPath(XElement element)
2406 {
2407 var builder = new StringBuilder();
2408
2409 while (element != null)
2410 {
2411 var index = element.Parent?.Elements().TakeWhile(e => e != element).Count() ?? 0;
2412 builder.Insert(0, $"/*[{index + 1}]");
2413
2414 element = element.Parent;
2415 }
2416
2417 return builder.ToString();
2418 }
2419
2420 private static SourceLineNumber SourceLineNumberForXmlLineInfo(string sourceFile, IXmlLineInfo lineInfo)
2421 {
2422 return (lineInfo?.HasLineInfo() == true) ? new SourceLineNumber(sourceFile, lineInfo.LineNumber) : new SourceLineNumber(sourceFile ?? "wix.exe");
2423 }
2424
2276 /// <summary> 2425 /// <summary>
2277 /// Return an identifier based on passed file/directory name 2426 /// Return an identifier based on passed file/directory name
2278 /// </summary> 2427 /// </summary>
@@ -2338,7 +2487,7 @@ namespace WixToolset.Converters
2338 comments = initialComments; 2487 comments = initialComments;
2339 var nonCommentNodes = new List<XNode>(); 2488 var nonCommentNodes = new List<XNode>();
2340 2489
2341 foreach(var node in nodes) 2490 foreach (var node in nodes)
2342 { 2491 {
2343 if (XmlNodeType.Comment == node.NodeType) 2492 if (XmlNodeType.Comment == node.NodeType)
2344 { 2493 {
diff --git a/src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs b/src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs
index d186931b..cd6849d3 100644
--- a/src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs
+++ b/src/wix/test/WixToolsetTest.Converters/ConverterFixture.cs
@@ -3,6 +3,8 @@
3namespace WixToolsetTest.Converters 3namespace WixToolsetTest.Converters
4{ 4{
5 using System; 5 using System;
6 using System.IO;
7 using System.Linq;
6 using System.Xml.Linq; 8 using System.Xml.Linq;
7 using WixBuildTools.TestSupport; 9 using WixBuildTools.TestSupport;
8 using WixToolset.Converters; 10 using WixToolset.Converters;
@@ -62,7 +64,7 @@ namespace WixToolsetTest.Converters
62 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); 64 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
63 65
64 var messaging = new MockMessaging(); 66 var messaging = new MockMessaging();
65 var converter = new WixConverter(messaging, 2, ignoreErrors: new[] { "DeclarationPresent" } ); 67 var converter = new WixConverter(messaging, 2, ignoreErrors: new[] { "DeclarationPresent" });
66 68
67 var errors = converter.ConvertDocument(document); 69 var errors = converter.ConvertDocument(document);
68 70
@@ -103,6 +105,40 @@ namespace WixToolsetTest.Converters
103 } 105 }
104 106
105 [Fact] 107 [Fact]
108 public void CanConvertMainNamespaceFromDisk()
109 {
110 var dataFolder = TestData.Get("TestData", "FixDeclarationAndNamespace");
111
112 var expected = new[]
113 {
114 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
115 " <Fragment />",
116 "</Wix>",
117 };
118
119 using (var fs = new TestDataFolderFileSystem())
120 {
121 fs.Initialize(dataFolder);
122 var path = Path.Combine(fs.BaseFolder, "FixDeclarationAndNamespace.wxs");
123
124 var messaging = new MockMessaging();
125 var converter = new WixConverter(messaging, 2, null, null);
126
127 var errors = converter.ConvertFile(path, true);
128
129 var messages = messaging.Messages.Select(m => $"{Path.GetFileName(m.SourceLineNumbers.FileName)}({m.SourceLineNumbers.LineNumber}) {m.ToString()}").ToArray();
130 WixAssert.CompareLineByLine(new[]
131 {
132 "FixDeclarationAndNamespace.wxs() [Converted] This file contains an XML declaration on the first line. (DeclarationPresent)",
133 "FixDeclarationAndNamespace.wxs(1) [Converted] The namespace 'http://schemas.microsoft.com/wix/2006/wi' is out of date. It must be 'http://wixtoolset.org/schemas/v4/wxs'. (XmlnsValueWrong)"
134 }, messages);
135
136 var actual = File.ReadAllLines(path);
137 WixAssert.CompareLineByLine(expected, actual);
138 }
139 }
140
141 [Fact]
106 public void CanConvertNamedMainNamespace() 142 public void CanConvertNamedMainNamespace()
107 { 143 {
108 var parse = String.Join(Environment.NewLine, 144 var parse = String.Join(Environment.NewLine,
diff --git a/src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs b/src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs
index a8908df7..fdebc6b0 100644
--- a/src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs
+++ b/src/wix/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs
@@ -85,9 +85,14 @@ namespace WixToolsetTest.Converters
85 85
86 var messaging = new MockMessaging(); 86 var messaging = new MockMessaging();
87 var converter = new WixConverter(messaging, 4); 87 var converter = new WixConverter(messaging, 4);
88 var errors = converter.ConvertFile(targetFile, true); 88 var convertedCount = converter.ConvertFile(targetFile, true);
89 89
90 Assert.Single(messaging.Messages.Where(m => m.Id == 5/*WixConverter.ConverterTestType.UnauthorizedAccessException*/)); 90 var errors = messaging.Messages.Where(m => m.Level == WixToolset.Data.MessageLevel.Error).Select(m => m.ToString()).ToArray();
91 WixAssert.CompareLineByLine(new[]
92 {
93 "Could not write to file. (UnauthorizedAccessException)"
94 }, errors);
95 Assert.Equal(10, convertedCount);
91 } 96 }
92 } 97 }
93 98
diff --git a/src/wix/test/WixToolsetTest.Converters/TestData/FixDeclarationAndNamespace/FixDeclarationAndNamespace.wxs b/src/wix/test/WixToolsetTest.Converters/TestData/FixDeclarationAndNamespace/FixDeclarationAndNamespace.wxs
new file mode 100644
index 00000000..a8923337
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Converters/TestData/FixDeclarationAndNamespace/FixDeclarationAndNamespace.wxs
@@ -0,0 +1,4 @@
1<?xml version='1.0' encoding='utf-8'?>
2<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
3 <Fragment />
4</Wix>