Separate "format" from "convert"
Closes wixtoolset/issues#6215
Rob Mensching committed
Jul 18, 2020 at 14:57 UTC
e04caab11fb8f2cac4d575ef1e352221bd421586
8 files changed
+589
-428
src/WixToolset.Converters/ConvertCommand.cs
+22
-247
@@ -3,283 +3,58 @@
3
namespace WixToolset.Converters
4
{
5
using System;
6
- using System.Collections.Generic;
7
- using System.IO;
6
using System.Threading;
7
using System.Threading.Tasks;
10
- using System.Xml;
11
- using WixToolset.Extensibility.Data;
8
using WixToolset.Extensibility.Services;
9
14
- internal class ConvertCommand : ICommandLineCommand
10
+ internal class ConvertCommand : FixupCommandBase
11
{
16
- private const string SettingsFileDefault = "wixcop.settings.xml";
12
+ private const string SettingsFileDefault = "wix.convert.settings.xml";
13
14
public ConvertCommand(IWixToolsetServiceProvider serviceProvider)
15
{
16
this.Messaging = serviceProvider.GetService<IMessaging>();
21
-
22
- this.IndentationAmount = 4; // default indentation amount
23
- this.ErrorsAsWarnings = new HashSet<string>();
24
- this.ExemptFiles = new HashSet<string>();
25
- this.IgnoreErrors = new HashSet<string>();
26
- this.SearchPatternResults = new HashSet<string>();
27
- this.SearchPatterns = new List<string>();
17
}
18
19
private IMessaging Messaging { get; }
20
32
- public bool ShowLogo { get; private set; }
33
-
34
- public bool StopParsing { get; private set; }
35
-
36
- private bool ShowHelp { get; set; }
37
-
38
- private HashSet<string> ErrorsAsWarnings { get; }
39
-
40
- private HashSet<string> ExemptFiles { get; }
41
-
42
- private bool FixErrors { get; set; }
43
-
44
- private int IndentationAmount { get; set; }
45
-
46
- private HashSet<string> IgnoreErrors { get; }
47
-
48
- private HashSet<string> SearchPatternResults { get; }
49
-
50
- private List<string> SearchPatterns { get; }
51
-
52
- private string SettingsFile1 { get; set; }
53
-
54
- private string SettingsFile2 { get; set; }
55
-
56
- private bool SubDirectories { get; set; }
57
-
58
- public bool TryParseArgument(ICommandLineParser parser, string argument)
59
- {
60
- if (!parser.IsSwitch(argument))
61
- {
62
- this.SearchPatterns.Add(argument);
63
- return true;
64
- }
65
-
66
- var parameter = argument.Substring(1);
67
- switch (parameter.ToLowerInvariant())
68
- {
69
- case "?":
70
- this.ShowHelp = true;
71
- this.ShowLogo = true;
72
- this.StopParsing = true;
73
- return true;
74
-
75
- case "f":
76
- this.FixErrors = true;
77
- return true;
78
-
79
- case "nologo":
80
- this.ShowLogo = false;
81
- return true;
82
-
83
- case "s":
84
- this.SubDirectories = true;
85
- return true;
86
-
87
- default: // other parameters
88
- if (parameter.StartsWith("set1", StringComparison.Ordinal))
89
- {
90
- this.SettingsFile1 = parameter.Substring(4);
91
- return true;
92
- }
93
- else if (parameter.StartsWith("set2", StringComparison.Ordinal))
94
- {
95
- this.SettingsFile2 = parameter.Substring(4);
96
- return true;
97
- }
98
- else if (parameter.StartsWith("indent:", StringComparison.Ordinal))
99
- {
100
- try
101
- {
102
- this.IndentationAmount = Convert.ToInt32(parameter.Substring(7));
103
- }
104
- catch
105
- {
106
- parser.ErrorArgument = parameter; // $"Invalid numeric argument: {parameter}";
107
- }
108
- return true;
109
- }
110
-
111
- return false;
112
- }
113
- }
114
-
115
- public Task<int> ExecuteAsync(CancellationToken cancellationToken)
21
+ public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
22
{
23
if (this.ShowHelp)
24
{
25
DisplayHelp();
120
- return Task.FromResult(1);
121
- }
122
-
123
- // parse the settings if any were specified
124
- if (null != this.SettingsFile1 || null != this.SettingsFile2)
125
- {
126
- this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2);
127
- }
128
- else
129
- {
130
- if (File.Exists(ConvertCommand.SettingsFileDefault))
131
- {
132
- this.ParseSettingsFiles(ConvertCommand.SettingsFileDefault, null);
133
- }
26
+ return Task.FromResult(-1);
27
}
28
29
var converter = new WixConverter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors);
30
138
- var errors = this.InspectSubDirectories(converter, Path.GetFullPath("."), cancellationToken);
31
+ this.ParseSettings(SettingsFileDefault);
32
+
33
+ var errors = base.Inspect(Inspector, cancellationToken);
34
140
- foreach (var searchPattern in this.SearchPatterns)
35
+ return Task.FromResult(errors);
36
+
37
+ int Inspector(string file, bool fix)
38
{
142
- if (!this.SearchPatternResults.Contains(searchPattern))
143
- {
144
- Console.Error.WriteLine("Could not find file \"{0}\"", searchPattern);
145
- errors++;
146
- }
39
+ return converter.ConvertFile(file, fix);
40
}
148
-
149
- return Task.FromResult(errors != 0 ? 2 : 0);
41
}
42
43
private static void DisplayHelp()
44
{
154
- Console.WriteLine(" usage: wix.exe convert sourceFile [sourceFile ...]");
45
Console.WriteLine();
156
- Console.WriteLine(" -f fix errors automatically for writable files");
157
- Console.WriteLine(" -nologo suppress displaying the logo information");
158
- Console.WriteLine(" -s search for matching files in current dir and subdirs");
159
- Console.WriteLine(" -set1<file> primary settings file");
160
- Console.WriteLine(" -set2<file> secondary settings file (overrides primary)");
161
- Console.WriteLine(" -indent:<n> indentation multiple (overrides default of 4)");
162
- Console.WriteLine(" -? this help information");
46
+ Console.WriteLine("Usage: wix convert [options] sourceFile [sourceFile ...]");
47
Console.WriteLine();
164
- Console.WriteLine(" sourceFile may use wildcards like *.wxs");
165
- }
166
-
167
- /// <summary>
168
- /// Get the files that match a search path pattern.
169
- /// </summary>
170
- /// <param name="baseDir">The base directory at which to begin the search.</param>
171
- /// <param name="searchPath">The search path pattern.</param>
172
- /// <returns>The files matching the pattern.</returns>
173
- private static string[] GetFiles(string baseDir, string searchPath)
174
- {
175
- // convert alternate directory separators to the standard one
176
- var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
177
- var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
178
- string[] files = null;
179
-
180
- try
181
- {
182
- if (0 > lastSeparator)
183
- {
184
- files = Directory.GetFiles(baseDir, filePath);
185
- }
186
- else // found directory separator
187
- {
188
- var searchPattern = filePath.Substring(lastSeparator + 1);
189
-
190
- files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern);
191
- }
192
- }
193
- catch (DirectoryNotFoundException)
194
- {
195
- // don't let this function throw the DirectoryNotFoundException. (this exception
196
- // occurs for non-existant directories and invalid characters in the searchPattern)
197
- }
198
-
199
- return files;
200
- }
201
-
202
- /// <summary>
203
- /// Inspect sub-directories.
204
- /// </summary>
205
- /// <param name="directory">The directory whose sub-directories will be inspected.</param>
206
- /// <returns>The number of errors that were found.</returns>
207
- private int InspectSubDirectories(WixConverter converter, string directory, CancellationToken cancellationToken)
208
- {
209
- var errors = 0;
210
-
211
- foreach (var searchPattern in this.SearchPatterns)
212
- {
213
- foreach (var sourceFilePath in GetFiles(directory, searchPattern))
214
- {
215
- cancellationToken.ThrowIfCancellationRequested();
216
-
217
- var file = new FileInfo(sourceFilePath);
218
-
219
- if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant()))
220
- {
221
- this.SearchPatternResults.Add(searchPattern);
222
- errors += converter.ConvertFile(file.FullName, this.FixErrors);
223
- }
224
- }
225
- }
226
-
227
- if (this.SubDirectories)
228
- {
229
- foreach (var childDirectoryPath in Directory.GetDirectories(directory))
230
- {
231
- errors += this.InspectSubDirectories(converter, childDirectoryPath, cancellationToken);
232
- }
233
- }
234
-
235
- return errors;
236
- }
237
-
238
- /// <summary>
239
- /// Parse the primary and secondary settings files.
240
- /// </summary>
241
- /// <param name="localSettingsFile1">The primary settings file.</param>
242
- /// <param name="localSettingsFile2">The secondary settings file.</param>
243
- private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2)
244
- {
245
- if (null == localSettingsFile1 && null != localSettingsFile2)
246
- {
247
- throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", nameof(localSettingsFile2));
248
- }
249
-
250
- var settingsFile = localSettingsFile1;
251
- while (null != settingsFile)
252
- {
253
- var doc = new XmlDocument();
254
- doc.Load(settingsFile);
255
-
256
- // get the types of tests that will have their errors displayed as warnings
257
- var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test");
258
- foreach (XmlElement test in testsIgnoredElements)
259
- {
260
- var key = test.GetAttribute("Id");
261
- this.IgnoreErrors.Add(key);
262
- }
263
-
264
- // get the types of tests that will have their errors displayed as warnings
265
- var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test");
266
- foreach (XmlElement test in testsAsWarningsElements)
267
- {
268
- var key = test.GetAttribute("Id");
269
- this.ErrorsAsWarnings.Add(key);
270
- }
271
-
272
- // get the exempt files
273
- var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File");
274
- foreach (XmlElement file in localExemptFiles)
275
- {
276
- var key = file.GetAttribute("Name").ToUpperInvariant();
277
- this.ExemptFiles.Add(key);
278
- }
279
-
280
- settingsFile = localSettingsFile2;
281
- localSettingsFile2 = null;
282
- }
48
+ Console.WriteLine("Options:");
49
+ Console.WriteLine(" -h|--help Show command line help.");
50
+ Console.WriteLine(" --nologo Suppress displaying the logo information.");
51
+ Console.WriteLine(" -n|--dry-run Only display errors, do not update files.");
52
+ Console.WriteLine(" -r|--recurse Search for matching files in current dir and subdirs.");
53
+ Console.WriteLine(" -set1<file> Primary settings file.");
54
+ Console.WriteLine(" -set2<file> Secondary settings file (overrides primary).");
55
+ Console.WriteLine(" -indent:<n> Indentation multiple (overrides default of 4).");
56
+ Console.WriteLine();
57
+ Console.WriteLine(" sourceFile may use wildcards like *.wxs");
58
}
59
}
60
}
src/WixToolset.Converters/ConverterExtensionCommandLine.cs
+9
-2
@@ -21,8 +21,11 @@ namespace WixToolset.Converters
21
22
private IWixToolsetServiceProvider ServiceProvider { get; }
23
24
- // TODO: Do something with CommandLineSwitches
25
- public override IEnumerable<ExtensionCommandLineSwitch> CommandLineSwitches => base.CommandLineSwitches;
24
+ public override IEnumerable<ExtensionCommandLineSwitch> CommandLineSwitches => new ExtensionCommandLineSwitch[]
25
+ {
26
+ new ExtensionCommandLineSwitch { Switch = "convert", Description = "Convert v3 source code to v4 source code." },
27
+ new ExtensionCommandLineSwitch { Switch = "format", Description = "Ensures consistent formatting of source code." },
28
+ };
29
30
public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command)
31
{
@@ -32,6 +35,10 @@ namespace WixToolset.Converters
35
{
36
command = new ConvertCommand(this.ServiceProvider);
37
}
38
+ else if ("format".Equals(argument, StringComparison.OrdinalIgnoreCase))
39
+ {
40
+ command = new FormatCommand(this.ServiceProvider);
41
+ }
42
43
return command != null;
44
}
src/WixToolset.Converters/FixupCommandBase.cs
new
+267
@@ -0,0 +1,267 @@
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
+
3
+namespace WixToolset.Converters
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.IO;
8
+ using System.Threading;
9
+ using System.Threading.Tasks;
10
+ using System.Xml;
11
+ using WixToolset.Extensibility.Data;
12
+ using WixToolset.Extensibility.Services;
13
+
14
+ internal abstract class FixupCommandBase : ICommandLineCommand
15
+ {
16
+ protected FixupCommandBase()
17
+ {
18
+ this.IndentationAmount = 4; // default indentation amount
19
+ this.ErrorsAsWarnings = new HashSet<string>();
20
+ this.ExemptFiles = new HashSet<string>();
21
+ this.IgnoreErrors = new HashSet<string>();
22
+ this.SearchPatternResults = new HashSet<string>();
23
+ this.SearchPatterns = new List<string>();
24
+ }
25
+
26
+ public bool ShowLogo { get; private set; }
27
+
28
+ public bool StopParsing { get; private set; }
29
+
30
+ protected bool ShowHelp { get; set; }
31
+
32
+ protected bool DryRun { get; set; }
33
+
34
+ protected HashSet<string> ErrorsAsWarnings { get; }
35
+
36
+ protected HashSet<string> IgnoreErrors { get; }
37
+
38
+ protected HashSet<string> ExemptFiles { get; }
39
+
40
+ protected int IndentationAmount { get; set; }
41
+
42
+ protected bool Recurse { get; set; }
43
+
44
+ private HashSet<string> SearchPatternResults { get; }
45
+
46
+ private List<string> SearchPatterns { get; }
47
+
48
+ private string SettingsFile1 { get; set; }
49
+
50
+ private string SettingsFile2 { get; set; }
51
+
52
+ public bool TryParseArgument(ICommandLineParser parser, string argument)
53
+ {
54
+ if (!parser.IsSwitch(argument))
55
+ {
56
+ this.SearchPatterns.Add(argument);
57
+ return true;
58
+ }
59
+
60
+ var parameter = argument.Substring(1);
61
+ switch (parameter.ToLowerInvariant())
62
+ {
63
+ case "?":
64
+ case "h":
65
+ case "-help":
66
+ this.ShowHelp = true;
67
+ this.ShowLogo = true;
68
+ this.StopParsing = true;
69
+ return true;
70
+
71
+ case "n":
72
+ case "--dry-run":
73
+ this.DryRun = true;
74
+ return true;
75
+
76
+ case "nologo":
77
+ case "-nologo":
78
+ this.ShowLogo = false;
79
+ return true;
80
+
81
+ case "s":
82
+ case "r":
83
+ case "-recurse":
84
+ this.Recurse = true;
85
+ return true;
86
+
87
+ default: // other parameters
88
+ if (parameter.StartsWith("set1", StringComparison.Ordinal))
89
+ {
90
+ this.SettingsFile1 = parameter.Substring(4);
91
+ return true;
92
+ }
93
+ else if (parameter.StartsWith("set2", StringComparison.Ordinal))
94
+ {
95
+ this.SettingsFile2 = parameter.Substring(4);
96
+ return true;
97
+ }
98
+ else if (parameter.StartsWith("indent:", StringComparison.Ordinal))
99
+ {
100
+ try
101
+ {
102
+ this.IndentationAmount = Convert.ToInt32(parameter.Substring(7));
103
+ }
104
+ catch
105
+ {
106
+ parser.ErrorArgument = parameter; // $"Invalid numeric argument: {parameter}";
107
+ }
108
+ return true;
109
+ }
110
+
111
+ return false;
112
+ }
113
+ }
114
+
115
+ public abstract Task<int> ExecuteAsync(CancellationToken cancellationToken);
116
+
117
+ protected void ParseSettings(string defaultSettingsFile)
118
+ {
119
+ // parse the settings if any were specified
120
+ if (null != this.SettingsFile1 || null != this.SettingsFile2)
121
+ {
122
+ this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2);
123
+ }
124
+ else
125
+ {
126
+ if (File.Exists(defaultSettingsFile))
127
+ {
128
+ this.ParseSettingsFiles(defaultSettingsFile, null);
129
+ }
130
+ }
131
+ }
132
+
133
+ protected int Inspect(Func<string, bool, int> inspector, CancellationToken cancellationToken)
134
+ {
135
+ var errors = this.InspectSubDirectories(inspector, Path.GetFullPath("."), cancellationToken);
136
+
137
+ foreach (var searchPattern in this.SearchPatterns)
138
+ {
139
+ if (!this.SearchPatternResults.Contains(searchPattern))
140
+ {
141
+ Console.Error.WriteLine("Could not find file \"{0}\"", searchPattern);
142
+ errors++;
143
+ }
144
+ }
145
+
146
+ return errors;
147
+ }
148
+
149
+ /// <summary>
150
+ /// Inspect sub-directories.
151
+ /// </summary>
152
+ /// <param name="directory">The directory whose sub-directories will be inspected.</param>
153
+ /// <returns>The number of errors that were found.</returns>
154
+ private int InspectSubDirectories(Func<string, bool, int> inspector, string directory, CancellationToken cancellationToken)
155
+ {
156
+ var errors = 0;
157
+
158
+ foreach (var searchPattern in this.SearchPatterns)
159
+ {
160
+ foreach (var sourceFilePath in GetFiles(directory, searchPattern))
161
+ {
162
+ cancellationToken.ThrowIfCancellationRequested();
163
+
164
+ var file = new FileInfo(sourceFilePath);
165
+
166
+ if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant()))
167
+ {
168
+ this.SearchPatternResults.Add(searchPattern);
169
+ errors += inspector(file.FullName, !this.DryRun);
170
+ }
171
+ }
172
+ }
173
+
174
+ if (this.Recurse)
175
+ {
176
+ foreach (var childDirectoryPath in Directory.GetDirectories(directory))
177
+ {
178
+ errors += this.InspectSubDirectories(inspector, childDirectoryPath, cancellationToken);
179
+ }
180
+ }
181
+
182
+ return errors;
183
+ }
184
+
185
+ /// <summary>
186
+ /// Parse the primary and secondary settings files.
187
+ /// </summary>
188
+ /// <param name="localSettingsFile1">The primary settings file.</param>
189
+ /// <param name="localSettingsFile2">The secondary settings file.</param>
190
+ private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2)
191
+ {
192
+ if (null == localSettingsFile1 && null != localSettingsFile2)
193
+ {
194
+ throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", nameof(localSettingsFile2));
195
+ }
196
+
197
+ var settingsFile = localSettingsFile1;
198
+ while (null != settingsFile)
199
+ {
200
+ var doc = new XmlDocument();
201
+ doc.Load(settingsFile);
202
+
203
+ // get the types of tests that will have their errors displayed as warnings
204
+ var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test");
205
+ foreach (XmlElement test in testsIgnoredElements)
206
+ {
207
+ var key = test.GetAttribute("Id");
208
+ this.IgnoreErrors.Add(key);
209
+ }
210
+
211
+ // get the types of tests that will have their errors displayed as warnings
212
+ var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test");
213
+ foreach (XmlElement test in testsAsWarningsElements)
214
+ {
215
+ var key = test.GetAttribute("Id");
216
+ this.ErrorsAsWarnings.Add(key);
217
+ }
218
+
219
+ // get the exempt files
220
+ var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File");
221
+ foreach (XmlElement file in localExemptFiles)
222
+ {
223
+ var key = file.GetAttribute("Name").ToUpperInvariant();
224
+ this.ExemptFiles.Add(key);
225
+ }
226
+
227
+ settingsFile = localSettingsFile2;
228
+ localSettingsFile2 = null;
229
+ }
230
+ }
231
+
232
+ /// <summary>
233
+ /// Get the files that match a search path pattern.
234
+ /// </summary>
235
+ /// <param name="baseDir">The base directory at which to begin the search.</param>
236
+ /// <param name="searchPath">The search path pattern.</param>
237
+ /// <returns>The files matching the pattern.</returns>
238
+ private static string[] GetFiles(string baseDir, string searchPath)
239
+ {
240
+ // convert alternate directory separators to the standard one
241
+ var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
242
+ var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
243
+ string[] files = null;
244
+
245
+ try
246
+ {
247
+ if (0 > lastSeparator)
248
+ {
249
+ files = Directory.GetFiles(baseDir, filePath);
250
+ }
251
+ else // found directory separator
252
+ {
253
+ var searchPattern = filePath.Substring(lastSeparator + 1);
254
+
255
+ files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern);
256
+ }
257
+ }
258
+ catch (DirectoryNotFoundException)
259
+ {
260
+ // don't let this function throw the DirectoryNotFoundException. (this exception
261
+ // occurs for non-existant directories and invalid characters in the searchPattern)
262
+ }
263
+
264
+ return files;
265
+ }
266
+ }
267
+}
src/WixToolset.Converters/FormatCommand.cs
new
+60
@@ -0,0 +1,60 @@
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
+
3
+namespace WixToolset.Converters
4
+{
5
+ using System;
6
+ using System.Threading;
7
+ using System.Threading.Tasks;
8
+ using WixToolset.Extensibility.Services;
9
+
10
+ internal class FormatCommand : FixupCommandBase
11
+ {
12
+ private const string SettingsFileDefault = "wix.format.settings.xml";
13
+
14
+ public FormatCommand(IWixToolsetServiceProvider serviceProvider)
15
+ {
16
+ this.Messaging = serviceProvider.GetService<IMessaging>();
17
+ }
18
+
19
+ private IMessaging Messaging { get; }
20
+
21
+ public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
22
+ {
23
+ if (this.ShowHelp)
24
+ {
25
+ DisplayHelp();
26
+ return Task.FromResult(-1);
27
+ }
28
+
29
+ var converter = new WixConverter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors);
30
+
31
+ this.ParseSettings(SettingsFileDefault);
32
+
33
+ var errors = base.Inspect(Inspector, cancellationToken);
34
+
35
+ return Task.FromResult(errors);
36
+
37
+ int Inspector(string file, bool fix)
38
+ {
39
+ return converter.FormatFile(file, fix);
40
+ }
41
+ }
42
+
43
+ private static void DisplayHelp()
44
+ {
45
+ Console.WriteLine();
46
+ Console.WriteLine("Usage: wix format [options] sourceFile [sourceFile ...]");
47
+ Console.WriteLine();
48
+ Console.WriteLine("Options:");
49
+ Console.WriteLine(" -h|--help Show command line help.");
50
+ Console.WriteLine(" --nologo Suppress displaying the logo information.");
51
+ Console.WriteLine(" -n|--dry-run Only display errors, do not update files.");
52
+ Console.WriteLine(" -r|--recurse Search for matching files in current dir and subdirs.");
53
+ Console.WriteLine(" -set1<file> Primary settings file.");
54
+ Console.WriteLine(" -set2<file> Secondary settings file (overrides primary).");
55
+ Console.WriteLine(" -indent:<n> Indentation multiple (overrides default of 4).");
56
+ Console.WriteLine();
57
+ Console.WriteLine(" sourceFile may use wildcards like *.wxs");
58
+ }
59
+ }
60
+}
src/WixToolset.Converters/WixConverter.cs
+109
-70
@@ -19,6 +19,12 @@ namespace WixToolset.Converters
19
/// </summary>
20
public class WixConverter
21
{
22
+ private enum ConvertOperation
23
+ {
24
+ Convert,
25
+ Format,
26
+ }
27
+
28
private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
29
private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
30
@@ -107,18 +113,6 @@ namespace WixToolset.Converters
113
{ "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" },
114
};
115
110
- private readonly static SortedSet<string> Wix3Namespaces = new SortedSet<string>
111
- {
112
- "http://schemas.microsoft.com/wix/2006/wi",
113
- "http://schemas.microsoft.com/wix/2006/localization",
114
- };
115
-
116
- private readonly static SortedSet<string> Wix4Namespaces = new SortedSet<string>
117
- {
118
- "http://wixtoolset.org/schemas/v4/wxs",
119
- "http://wixtoolset.org/schemas/v4/wxl",
120
- };
121
-
116
private readonly Dictionary<XName, Action<XElement>> ConvertElementMapping;
117
118
/// <summary>
@@ -193,6 +187,8 @@ namespace WixToolset.Converters
187
188
private int IndentationAmount { get; set; }
189
190
+ private ConvertOperation Operation { get; set; }
191
+
192
private string SourceFile { get; set; }
193
194
private int SourceVersion { get; set; }
@@ -205,22 +201,11 @@ namespace WixToolset.Converters
201
/// <returns>The number of errors found.</returns>
202
public int ConvertFile(string sourceFile, bool saveConvertedFile)
203
{
208
- XDocument document;
209
-
210
- // Set the instance info.
211
- this.Errors = 0;
212
- this.SourceFile = sourceFile;
213
- this.SourceVersion = 0;
204
+ var document = this.OpenSourceFile(sourceFile);
205
215
- try
206
+ if (document is null)
207
{
217
- document = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
218
- }
219
- catch (XmlException e)
220
- {
221
- this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message);
222
-
223
- return this.Errors;
208
+ return 1;
209
}
210
211
this.ConvertDocument(document);
@@ -228,17 +213,7 @@ namespace WixToolset.Converters
213
// Fix errors if requested and necessary.
214
if (saveConvertedFile && 0 < this.Errors)
215
{
231
- try
232
- {
233
- using (var writer = XmlWriter.Create(this.SourceFile, new XmlWriterSettings { OmitXmlDeclaration = true }))
234
- {
235
- document.Save(writer);
236
- }
237
- }
238
- catch (UnauthorizedAccessException)
239
- {
240
- this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file.");
241
- }
216
+ this.SaveDocument(document);
217
}
218
219
return this.Errors;
@@ -251,13 +226,68 @@ namespace WixToolset.Converters
226
/// <returns>The number of errors found.</returns>
227
public int ConvertDocument(XDocument document)
228
{
229
+ // Reset the instance info.
230
this.Errors = 0;
231
this.SourceVersion = 0;
232
+ this.Operation = ConvertOperation.Convert;
233
+
234
+ // Remove the declaration.
235
+ if (null != document.Declaration)
236
+ {
237
+ if (this.OnError(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line."))
238
+ {
239
+ document.Declaration = null;
240
+ }
241
+ }
242
+
243
+ TrimLeadingText(document);
244
+
245
+ // Start converting the nodes at the top.
246
+ this.ConvertNodes(document.Nodes(), 0);
247
+
248
+ return this.Errors;
249
+ }
250
257
- var declaration = document.Declaration;
251
+ /// <summary>
252
+ /// Format a file.
253
+ /// </summary>
254
+ /// <param name="sourceFile">The file to format.</param>
255
+ /// <param name="saveConvertedFile">Option to save the format errors that are found.</param>
256
+ /// <returns>The number of errors found.</returns>
257
+ public int FormatFile(string sourceFile, bool saveConvertedFile)
258
+ {
259
+ var document = this.OpenSourceFile(sourceFile);
260
+
261
+ if (document is null)
262
+ {
263
+ return 1;
264
+ }
265
+
266
+ this.FormatDocument(document);
267
+
268
+ // Fix errors if requested and necessary.
269
+ if (saveConvertedFile && 0 < this.Errors)
270
+ {
271
+ this.SaveDocument(document);
272
+ }
273
+
274
+ return this.Errors;
275
+ }
276
+
277
+ /// <summary>
278
+ /// Format a document.
279
+ /// </summary>
280
+ /// <param name="document">The document to format.</param>
281
+ /// <returns>The number of errors found.</returns>
282
+ public int FormatDocument(XDocument document)
283
+ {
284
+ // Reset the instance info.
285
+ this.Errors = 0;
286
+ this.SourceVersion = 0;
287
+ this.Operation = ConvertOperation.Format;
288
289
// Remove the declaration.
260
- if (null != declaration)
290
+ if (null != document.Declaration)
291
{
292
if (this.OnError(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line."))
293
{
@@ -273,6 +303,37 @@ namespace WixToolset.Converters
303
return this.Errors;
304
}
305
306
+ private XDocument OpenSourceFile(string sourceFile)
307
+ {
308
+ this.SourceFile = sourceFile;
309
+
310
+ try
311
+ {
312
+ return XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
313
+ }
314
+ catch (XmlException e)
315
+ {
316
+ this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message);
317
+ }
318
+
319
+ return null;
320
+ }
321
+
322
+ private void SaveDocument(XDocument document)
323
+ {
324
+ try
325
+ {
326
+ using (var writer = XmlWriter.Create(this.SourceFile, new XmlWriterSettings { OmitXmlDeclaration = true }))
327
+ {
328
+ document.Save(writer);
329
+ }
330
+ }
331
+ catch (UnauthorizedAccessException)
332
+ {
333
+ this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file.");
334
+ }
335
+ }
336
+
337
private void ConvertNodes(IEnumerable<XNode> nodes, int level)
338
{
339
// Note we operate on a copy of the node list since we may
@@ -901,7 +962,10 @@ namespace WixToolset.Converters
962
/// <returns>Returns true indicating that action should be taken on this error, and false if it should be ignored.</returns>
963
private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args)
964
{
904
- if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error
965
+ // Ignore the error if explicitly ignored or outside the range of the current operation.
966
+ if (this.IgnoreErrors.Contains(converterTestType) ||
967
+ (this.Operation == ConvertOperation.Convert && converterTestType < ConverterTestType.DeclarationPresent) ||
968
+ (this.Operation == ConvertOperation.Format && converterTestType > ConverterTestType.DeclarationPresent))
969
{
970
return false;
971
}
@@ -909,7 +973,7 @@ namespace WixToolset.Converters
973
// Increase the error count.
974
this.Errors++;
975
912
- var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber);
976
+ var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wix.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber);
977
var warning = this.ErrorsAsWarnings.Contains(converterTestType);
978
var display = String.Format(CultureInfo.CurrentCulture, message, args);
979
@@ -1049,40 +1113,20 @@ namespace WixToolset.Converters
1113
/// </summary>
1114
UnauthorizedAccessException,
1115
1052
- /// <summary>
1053
- /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'.
1054
- /// </summary>
1055
- DeclarationEncodingWrong,
1056
-
1057
- /// <summary>
1058
- /// Displayed when the XML declaration is missing from the source file.
1059
- /// </summary>
1060
- DeclarationMissing,
1061
-
1062
- /// <summary>
1063
- /// Displayed when the whitespace preceding a CDATA node is wrong.
1064
- /// </summary>
1065
- WhitespacePrecedingCDATAWrong,
1066
-
1116
/// <summary>
1117
/// Displayed when the whitespace preceding a node is wrong.
1118
/// </summary>
1119
WhitespacePrecedingNodeWrong,
1120
1121
/// <summary>
1073
- /// Displayed when an element is not empty as it should be.
1074
- /// </summary>
1075
- NotEmptyElement,
1076
-
1077
- /// <summary>
1078
- /// Displayed when the whitespace following a CDATA node is wrong.
1122
+ /// Displayed when the whitespace preceding an end element is wrong.
1123
/// </summary>
1080
- WhitespaceFollowingCDATAWrong,
1124
+ WhitespacePrecedingEndElementWrong,
1125
1126
/// <summary>
1083
- /// Displayed when the whitespace preceding an end element is wrong.
1127
+ /// Displayed when the XML declaration is present in the source file.
1128
/// </summary>
1085
- WhitespacePrecedingEndElementWrong,
1129
+ DeclarationPresent,
1130
1131
/// <summary>
1132
/// Displayed when the xmlns attribute is missing from the document element.
@@ -1154,11 +1198,6 @@ namespace WixToolset.Converters
1198
/// </summary>
1199
AutoGuidUnnecessary,
1200
1157
- /// <summary>
1158
- /// Displayed when the XML declaration is present in the source file.
1159
- /// </summary>
1160
- DeclarationPresent,
1161
-
1201
/// <summary>
1202
/// The Feature Absent attribute renamed to AllowAbsent.
1203
/// </summary>
src/test/WixToolsetTest.Converters/ConverterFixture.cs
-104
@@ -39,110 +39,6 @@ namespace WixToolsetTest.Converters
39
Assert.Equal(expected, actual);
40
}
41
42
- [Fact]
43
- public void CanFixWhitespace()
44
- {
45
- var parse = String.Join(Environment.NewLine,
46
- "<?xml version='1.0' encoding='utf-8'?>",
47
- "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
48
- " <Fragment>",
49
- " <Property Id='Prop'",
50
- " Value='Val'>",
51
- " </Property>",
52
- " </Fragment>",
53
- "</Wix>");
54
-
55
- var expected = String.Join(Environment.NewLine,
56
- "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
57
- " <Fragment>",
58
- " <Property Id=\"Prop\" Value=\"Val\" />",
59
- " </Fragment>",
60
- "</Wix>");
61
-
62
- var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
63
-
64
- var messaging = new MockMessaging();
65
- var converter = new WixConverter(messaging, 4, null, null);
66
-
67
- var errors = converter.ConvertDocument(document);
68
-
69
- var actual = UnformattedDocumentString(document);
70
-
71
- Assert.Equal(expected, actual);
72
- Assert.Equal(5, errors);
73
- }
74
-
75
- [Fact]
76
- public void CanPreserveNewLines()
77
- {
78
- var parse = String.Join(Environment.NewLine,
79
- "<?xml version='1.0' encoding='utf-8'?>",
80
- "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
81
- " <Fragment>",
82
- "",
83
- " <Property Id='Prop' Value='Val' />",
84
- "",
85
- " </Fragment>",
86
- "</Wix>");
87
-
88
- var expected = String.Join(Environment.NewLine,
89
- "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
90
- " <Fragment>",
91
- "",
92
- " <Property Id=\"Prop\" Value=\"Val\" />",
93
- "",
94
- " </Fragment>",
95
- "</Wix>");
96
-
97
- var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
98
-
99
- var messaging = new MockMessaging();
100
- var converter = new WixConverter(messaging, 4, null, null);
101
-
102
- var conversions = converter.ConvertDocument(document);
103
-
104
- var actual = UnformattedDocumentString(document);
105
-
106
- Assert.Equal(expected, actual);
107
- Assert.Equal(4, conversions);
108
- }
109
-
110
- [Fact]
111
- public void CanConvertWithNewLineAtEndOfFile()
112
- {
113
- var parse = String.Join(Environment.NewLine,
114
- "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
115
- " <Fragment>",
116
- "",
117
- " <Property Id='Prop' Value='Val' />",
118
- "",
119
- " </Fragment>",
120
- "</Wix>",
121
- "");
122
-
123
- var expected = String.Join(Environment.NewLine,
124
- "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
125
- " <Fragment>",
126
- "",
127
- " <Property Id=\"Prop\" Value=\"Val\" />",
128
- "",
129
- " </Fragment>",
130
- "</Wix>",
131
- "");
132
-
133
- var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
134
-
135
- var messaging = new MockMessaging();
136
- var converter = new WixConverter(messaging, 4, null, null);
137
-
138
- var conversions = converter.ConvertDocument(document);
139
-
140
- var actual = UnformattedDocumentString(document);
141
-
142
- Assert.Equal(expected, actual);
143
- Assert.Equal(3, conversions);
144
- }
145
-
42
[Fact]
43
public void CanConvertMainNamespace()
44
{
src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs
+5
-5
@@ -84,7 +84,7 @@ namespace WixToolsetTest.Converters
84
var settingsFile = Path.Combine(folder, "wixcop.settings.xml");
85
86
var result = RunConversion(targetFile, settingsFile: settingsFile);
87
- Assert.Equal(2, result.ExitCode);
87
+ Assert.Equal(7, result.ExitCode);
88
89
var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n");
90
var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n");
@@ -108,7 +108,7 @@ namespace WixToolsetTest.Converters
108
File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName));
109
110
var result = RunConversion(targetFile);
111
- Assert.Equal(2, result.ExitCode);
111
+ Assert.Equal(10, result.ExitCode);
112
113
var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n");
114
var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n");
@@ -133,7 +133,7 @@ namespace WixToolsetTest.Converters
133
134
var result = RunConversion(targetFile);
135
136
- Assert.Equal(2, result.ExitCode);
136
+ Assert.Equal(10, result.ExitCode);
137
Assert.Single(result.Messages.Where(message => message.ToString().EndsWith("(QtExecCmdTimeoutAmbiguous)")));
138
139
var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n");
@@ -142,7 +142,7 @@ namespace WixToolsetTest.Converters
142
143
// still fails because QtExecCmdTimeoutAmbiguous is unfixable
144
var result2 = RunConversion(targetFile);
145
- Assert.Equal(2, result2.ExitCode);
145
+ Assert.Equal(1, result2.ExitCode);
146
}
147
}
148
@@ -153,7 +153,7 @@ namespace WixToolsetTest.Converters
153
var exitCode = WixRunner.Execute(new[]
154
{
155
"convert",
156
- fixErrors ? "-f" : null,
156
+ fixErrors ? null : "--dry-run",
157
String.IsNullOrEmpty(settingsFile) ? null : "-set1" + settingsFile,
158
targetFile
159
}, serviceProvider, out var messages);
src/test/WixToolsetTest.Converters/FormatFixture.cs
new
+117
@@ -0,0 +1,117 @@
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
+
3
+namespace WixToolsetTest.Converters
4
+{
5
+ using System;
6
+ using System.Xml.Linq;
7
+ using WixToolset.Converters;
8
+ using WixToolsetTest.Converters.Mocks;
9
+ using Xunit;
10
+
11
+ public class FormatFixture : BaseConverterFixture
12
+ {
13
+ [Fact]
14
+ public void CanFixWhitespace()
15
+ {
16
+ var parse = String.Join(Environment.NewLine,
17
+ "<?xml version='1.0' encoding='utf-8'?>",
18
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
19
+ " <Fragment>",
20
+ " <Property Id='Prop'",
21
+ " Value='Val'>",
22
+ " </Property>",
23
+ " </Fragment>",
24
+ "</Wix>");
25
+
26
+ var expected = String.Join(Environment.NewLine,
27
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
28
+ " <Fragment>",
29
+ " <Property Id=\"Prop\" Value=\"Val\" />",
30
+ " </Fragment>",
31
+ "</Wix>");
32
+
33
+ var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
34
+
35
+ var messaging = new MockMessaging();
36
+ var converter = new WixConverter(messaging, 4, null, null);
37
+
38
+ var errors = converter.FormatDocument(document);
39
+
40
+ var actual = UnformattedDocumentString(document);
41
+
42
+ Assert.Equal(expected, actual);
43
+ Assert.Equal(5, errors);
44
+ }
45
+
46
+ [Fact]
47
+ public void CanPreserveNewLines()
48
+ {
49
+ var parse = String.Join(Environment.NewLine,
50
+ "<?xml version='1.0' encoding='utf-8'?>",
51
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
52
+ " <Fragment>",
53
+ "",
54
+ " <Property Id='Prop' Value='Val' />",
55
+ "",
56
+ " </Fragment>",
57
+ "</Wix>");
58
+
59
+ var expected = String.Join(Environment.NewLine,
60
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
61
+ " <Fragment>",
62
+ "",
63
+ " <Property Id=\"Prop\" Value=\"Val\" />",
64
+ "",
65
+ " </Fragment>",
66
+ "</Wix>");
67
+
68
+ var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
69
+
70
+ var messaging = new MockMessaging();
71
+ var converter = new WixConverter(messaging, 4, null, null);
72
+
73
+ var conversions = converter.FormatDocument(document);
74
+
75
+ var actual = UnformattedDocumentString(document);
76
+
77
+ Assert.Equal(expected, actual);
78
+ Assert.Equal(4, conversions);
79
+ }
80
+
81
+ [Fact]
82
+ public void CanFormatWithNewLineAtEndOfFile()
83
+ {
84
+ var parse = String.Join(Environment.NewLine,
85
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
86
+ " <Fragment>",
87
+ "",
88
+ " <Property Id='Prop' Value='Val' />",
89
+ "",
90
+ " </Fragment>",
91
+ "</Wix>",
92
+ "");
93
+
94
+ var expected = String.Join(Environment.NewLine,
95
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
96
+ " <Fragment>",
97
+ "",
98
+ " <Property Id=\"Prop\" Value=\"Val\" />",
99
+ "",
100
+ " </Fragment>",
101
+ "</Wix>",
102
+ "");
103
+
104
+ var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
105
+
106
+ var messaging = new MockMessaging();
107
+ var converter = new WixConverter(messaging, 4, null, null);
108
+
109
+ var conversions = converter.FormatDocument(document);
110
+
111
+ var actual = UnformattedDocumentString(document);
112
+
113
+ Assert.Equal(expected, actual);
114
+ Assert.Equal(3, conversions);
115
+ }
116
+ }
117
+}