diff options
Diffstat (limited to 'src/wixcop')
-rw-r--r-- | src/wixcop/CommandLine/ConvertCommand.cs | 212 | ||||
-rw-r--r-- | src/wixcop/CommandLine/HelpCommand.cs | 25 | ||||
-rw-r--r-- | src/wixcop/CommandLine/WixCopCommandLineParser.cs | 132 | ||||
-rw-r--r-- | src/wixcop/Converter.cs | 633 | ||||
-rw-r--r-- | src/wixcop/Interfaces/IWixCopCommandLineParser.cs | 11 | ||||
-rw-r--r-- | src/wixcop/Program.cs | 67 | ||||
-rw-r--r-- | src/wixcop/WixCop.csproj | 32 |
7 files changed, 1112 insertions, 0 deletions
diff --git a/src/wixcop/CommandLine/ConvertCommand.cs b/src/wixcop/CommandLine/ConvertCommand.cs new file mode 100644 index 00000000..6af7d4ca --- /dev/null +++ b/src/wixcop/CommandLine/ConvertCommand.cs | |||
@@ -0,0 +1,212 @@ | |||
1 | using System; | ||
2 | using System.Collections.Generic; | ||
3 | using System.IO; | ||
4 | using System.Linq; | ||
5 | using System.Xml; | ||
6 | using WixToolset.Extensibility.Data; | ||
7 | using WixToolset.Extensibility.Services; | ||
8 | |||
9 | namespace WixCop.CommandLine | ||
10 | { | ||
11 | internal class ConvertCommand : ICommandLineCommand | ||
12 | { | ||
13 | private const string SettingsFileDefault = "wixcop.settings.xml"; | ||
14 | |||
15 | public ConvertCommand(IServiceProvider serviceProvider, bool fixErrors, int indentationAmount, List<string> searchPatterns, bool subDirectories, string settingsFile1, string settingsFile2) | ||
16 | { | ||
17 | this.ErrorsAsWarnings = new HashSet<string>(); | ||
18 | this.ExemptFiles = new HashSet<string>(); | ||
19 | this.FixErrors = fixErrors; | ||
20 | this.IndentationAmount = indentationAmount; | ||
21 | this.IgnoreErrors = new HashSet<string>(); | ||
22 | this.SearchPatternResults = new HashSet<string>(); | ||
23 | this.SearchPatterns = searchPatterns; | ||
24 | this.ServiceProvider = serviceProvider; | ||
25 | this.SettingsFile1 = settingsFile1; | ||
26 | this.SettingsFile2 = settingsFile2; | ||
27 | this.SubDirectories = subDirectories; | ||
28 | } | ||
29 | |||
30 | private HashSet<string> ErrorsAsWarnings { get; } | ||
31 | |||
32 | private HashSet<string> ExemptFiles { get; } | ||
33 | |||
34 | private bool FixErrors { get; } | ||
35 | |||
36 | private int IndentationAmount { get; } | ||
37 | |||
38 | private HashSet<string> IgnoreErrors { get; } | ||
39 | |||
40 | private HashSet<string> SearchPatternResults { get; } | ||
41 | |||
42 | private List<string> SearchPatterns { get; } | ||
43 | |||
44 | private IServiceProvider ServiceProvider { get; } | ||
45 | |||
46 | private string SettingsFile1 { get; } | ||
47 | |||
48 | private string SettingsFile2 { get; } | ||
49 | |||
50 | private bool SubDirectories { get; } | ||
51 | |||
52 | public int Execute() | ||
53 | { | ||
54 | // parse the settings if any were specified | ||
55 | if (null != this.SettingsFile1 || null != this.SettingsFile2) | ||
56 | { | ||
57 | this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2); | ||
58 | } | ||
59 | else | ||
60 | { | ||
61 | if (File.Exists(ConvertCommand.SettingsFileDefault)) | ||
62 | { | ||
63 | this.ParseSettingsFiles(ConvertCommand.SettingsFileDefault, null); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | var messaging = this.ServiceProvider.GetService<IMessaging>(); | ||
68 | var converter = new Converter(messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors); | ||
69 | |||
70 | var errors = this.InspectSubDirectories(converter, Path.GetFullPath(".")); | ||
71 | |||
72 | foreach (string searchPattern in this.SearchPatterns) | ||
73 | { | ||
74 | if (!this.SearchPatternResults.Contains(searchPattern)) | ||
75 | { | ||
76 | Console.Error.WriteLine("Could not find file \"{0}\"", searchPattern); | ||
77 | errors++; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | return errors != 0 ? 2 : 0; | ||
82 | } | ||
83 | |||
84 | /// <summary> | ||
85 | /// Get the files that match a search path pattern. | ||
86 | /// </summary> | ||
87 | /// <param name="baseDir">The base directory at which to begin the search.</param> | ||
88 | /// <param name="searchPath">The search path pattern.</param> | ||
89 | /// <returns>The files matching the pattern.</returns> | ||
90 | private static string[] GetFiles(string baseDir, string searchPath) | ||
91 | { | ||
92 | // convert alternate directory separators to the standard one | ||
93 | var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); | ||
94 | var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); | ||
95 | string[] files = null; | ||
96 | |||
97 | try | ||
98 | { | ||
99 | if (0 > lastSeparator) | ||
100 | { | ||
101 | files = Directory.GetFiles(baseDir, filePath); | ||
102 | } | ||
103 | else // found directory separator | ||
104 | { | ||
105 | var searchPattern = filePath.Substring(lastSeparator + 1); | ||
106 | |||
107 | files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern); | ||
108 | } | ||
109 | } | ||
110 | catch (DirectoryNotFoundException) | ||
111 | { | ||
112 | // don't let this function throw the DirectoryNotFoundException. (this exception | ||
113 | // occurs for non-existant directories and invalid characters in the searchPattern) | ||
114 | } | ||
115 | |||
116 | return files; | ||
117 | } | ||
118 | |||
119 | /// <summary> | ||
120 | /// Inspect sub-directories. | ||
121 | /// </summary> | ||
122 | /// <param name="directory">The directory whose sub-directories will be inspected.</param> | ||
123 | /// <returns>The number of errors that were found.</returns> | ||
124 | private int InspectSubDirectories(Converter converter, string directory) | ||
125 | { | ||
126 | var errors = 0; | ||
127 | |||
128 | foreach (var searchPattern in this.SearchPatterns) | ||
129 | { | ||
130 | foreach (var sourceFilePath in GetFiles(directory, searchPattern)) | ||
131 | { | ||
132 | var file = new FileInfo(sourceFilePath); | ||
133 | |||
134 | if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant())) | ||
135 | { | ||
136 | this.SearchPatternResults.Add(searchPattern); | ||
137 | errors += converter.ConvertFile(file.FullName, this.FixErrors); | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | |||
142 | if (this.SubDirectories) | ||
143 | { | ||
144 | foreach (var childDirectoryPath in Directory.GetDirectories(directory)) | ||
145 | { | ||
146 | errors += this.InspectSubDirectories(converter, childDirectoryPath); | ||
147 | } | ||
148 | } | ||
149 | |||
150 | return errors; | ||
151 | } | ||
152 | |||
153 | /// <summary> | ||
154 | /// Parse the primary and secondary settings files. | ||
155 | /// </summary> | ||
156 | /// <param name="localSettingsFile1">The primary settings file.</param> | ||
157 | /// <param name="localSettingsFile2">The secondary settings file.</param> | ||
158 | private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2) | ||
159 | { | ||
160 | if (null == localSettingsFile1 && null != localSettingsFile2) | ||
161 | { | ||
162 | throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", "localSettingsFile2"); | ||
163 | } | ||
164 | |||
165 | var settingsFile = localSettingsFile1; | ||
166 | while (null != settingsFile) | ||
167 | { | ||
168 | XmlTextReader reader = null; | ||
169 | try | ||
170 | { | ||
171 | reader = new XmlTextReader(settingsFile); | ||
172 | var doc = new XmlDocument(); | ||
173 | doc.Load(reader); | ||
174 | |||
175 | // get the types of tests that will have their errors displayed as warnings | ||
176 | var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test"); | ||
177 | foreach (XmlElement test in testsIgnoredElements) | ||
178 | { | ||
179 | var key = test.GetAttribute("Id"); | ||
180 | this.IgnoreErrors.Add(key); | ||
181 | } | ||
182 | |||
183 | // get the types of tests that will have their errors displayed as warnings | ||
184 | var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test"); | ||
185 | foreach (XmlElement test in testsAsWarningsElements) | ||
186 | { | ||
187 | var key = test.GetAttribute("Id"); | ||
188 | this.ErrorsAsWarnings.Add(key); | ||
189 | } | ||
190 | |||
191 | // get the exempt files | ||
192 | var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File"); | ||
193 | foreach (XmlElement file in localExemptFiles) | ||
194 | { | ||
195 | var key = file.GetAttribute("Name").ToUpperInvariant(); | ||
196 | this.ExemptFiles.Add(key); | ||
197 | } | ||
198 | } | ||
199 | finally | ||
200 | { | ||
201 | if (null != reader) | ||
202 | { | ||
203 | reader.Close(); | ||
204 | } | ||
205 | } | ||
206 | |||
207 | settingsFile = localSettingsFile2; | ||
208 | localSettingsFile2 = null; | ||
209 | } | ||
210 | } | ||
211 | } | ||
212 | } | ||
diff --git a/src/wixcop/CommandLine/HelpCommand.cs b/src/wixcop/CommandLine/HelpCommand.cs new file mode 100644 index 00000000..a75dac5c --- /dev/null +++ b/src/wixcop/CommandLine/HelpCommand.cs | |||
@@ -0,0 +1,25 @@ | |||
1 | using System; | ||
2 | using WixToolset.Extensibility.Data; | ||
3 | |||
4 | namespace WixCop.CommandLine | ||
5 | { | ||
6 | internal class HelpCommand : ICommandLineCommand | ||
7 | { | ||
8 | public int Execute() | ||
9 | { | ||
10 | Console.WriteLine(" usage: wixcop.exe sourceFile [sourceFile ...]"); | ||
11 | Console.WriteLine(); | ||
12 | Console.WriteLine(" -f fix errors automatically for writable files"); | ||
13 | Console.WriteLine(" -nologo suppress displaying the logo information"); | ||
14 | Console.WriteLine(" -s search for matching files in current dir and subdirs"); | ||
15 | Console.WriteLine(" -set1<file> primary settings file"); | ||
16 | Console.WriteLine(" -set2<file> secondary settings file (overrides primary)"); | ||
17 | Console.WriteLine(" -indent:<n> indentation multiple (overrides default of 4)"); | ||
18 | Console.WriteLine(" -? this help information"); | ||
19 | Console.WriteLine(); | ||
20 | Console.WriteLine(" sourceFile may use wildcards like *.wxs"); | ||
21 | |||
22 | return 0; | ||
23 | } | ||
24 | } | ||
25 | } | ||
diff --git a/src/wixcop/CommandLine/WixCopCommandLineParser.cs b/src/wixcop/CommandLine/WixCopCommandLineParser.cs new file mode 100644 index 00000000..53012cfd --- /dev/null +++ b/src/wixcop/CommandLine/WixCopCommandLineParser.cs | |||
@@ -0,0 +1,132 @@ | |||
1 | using System; | ||
2 | using System.Collections.Generic; | ||
3 | using WixCop.Interfaces; | ||
4 | using WixToolset.Core; | ||
5 | using WixToolset.Extensibility.Data; | ||
6 | using WixToolset.Extensibility.Services; | ||
7 | |||
8 | namespace WixCop.CommandLine | ||
9 | { | ||
10 | public sealed class WixCopCommandLineParser : IWixCopCommandLineParser | ||
11 | { | ||
12 | private bool fixErrors; | ||
13 | private int indentationAmount; | ||
14 | private readonly List<string> searchPatterns; | ||
15 | private readonly IServiceProvider serviceProvider; | ||
16 | private string settingsFile1; | ||
17 | private string settingsFile2; | ||
18 | private bool showHelp; | ||
19 | private bool showLogo; | ||
20 | private bool subDirectories; | ||
21 | |||
22 | public WixCopCommandLineParser(IServiceProvider serviceProvider) | ||
23 | { | ||
24 | this.serviceProvider = serviceProvider; | ||
25 | |||
26 | this.indentationAmount = 4; | ||
27 | this.searchPatterns = new List<string>(); | ||
28 | this.showLogo = true; | ||
29 | } | ||
30 | |||
31 | public ICommandLineArguments Arguments { get; set; } | ||
32 | |||
33 | public ICommandLineCommand ParseWixCopCommandLine() | ||
34 | { | ||
35 | this.Parse(); | ||
36 | |||
37 | if (this.showLogo) | ||
38 | { | ||
39 | AppCommon.DisplayToolHeader(); | ||
40 | Console.WriteLine(); | ||
41 | } | ||
42 | |||
43 | if (this.showHelp) | ||
44 | { | ||
45 | return new HelpCommand(); | ||
46 | } | ||
47 | |||
48 | return new ConvertCommand( | ||
49 | this.serviceProvider, | ||
50 | this.fixErrors, | ||
51 | this.indentationAmount, | ||
52 | this.searchPatterns, | ||
53 | this.subDirectories, | ||
54 | this.settingsFile1, | ||
55 | this.settingsFile2); | ||
56 | } | ||
57 | |||
58 | private void Parse() | ||
59 | { | ||
60 | this.showHelp = 0 == this.Arguments.Arguments.Length; | ||
61 | var parser = this.Arguments.Parse(); | ||
62 | |||
63 | while (!this.showHelp && | ||
64 | String.IsNullOrEmpty(parser.ErrorArgument) && | ||
65 | parser.TryGetNextSwitchOrArgument(out var arg)) | ||
66 | { | ||
67 | if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. | ||
68 | { | ||
69 | continue; | ||
70 | } | ||
71 | |||
72 | if (parser.IsSwitch(arg)) | ||
73 | { | ||
74 | if (!this.ParseArgument(parser, arg)) | ||
75 | { | ||
76 | parser.ErrorArgument = arg; | ||
77 | } | ||
78 | } | ||
79 | else | ||
80 | { | ||
81 | this.searchPatterns.Add(arg); | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | |||
86 | private bool ParseArgument(IParseCommandLine parser, string arg) | ||
87 | { | ||
88 | var parameter = arg.Substring(1); | ||
89 | |||
90 | switch (parameter.ToLowerInvariant()) | ||
91 | { | ||
92 | case "?": | ||
93 | this.showHelp = true; | ||
94 | return true; | ||
95 | case "f": | ||
96 | this.fixErrors = true; | ||
97 | return true; | ||
98 | case "nologo": | ||
99 | this.showLogo = false; | ||
100 | return true; | ||
101 | case "s": | ||
102 | this.subDirectories = true; | ||
103 | return true; | ||
104 | default: // other parameters | ||
105 | if (parameter.StartsWith("set1", StringComparison.Ordinal)) | ||
106 | { | ||
107 | this.settingsFile1 = parameter.Substring(4); | ||
108 | } | ||
109 | else if (parameter.StartsWith("set2", StringComparison.Ordinal)) | ||
110 | { | ||
111 | this.settingsFile2 = parameter.Substring(4); | ||
112 | } | ||
113 | else if (parameter.StartsWith("indent:", StringComparison.Ordinal)) | ||
114 | { | ||
115 | try | ||
116 | { | ||
117 | this.indentationAmount = Convert.ToInt32(parameter.Substring(7)); | ||
118 | } | ||
119 | catch | ||
120 | { | ||
121 | throw new ArgumentException("Invalid numeric argument.", parameter); | ||
122 | } | ||
123 | } | ||
124 | else | ||
125 | { | ||
126 | throw new ArgumentException("Invalid argument.", parameter); | ||
127 | } | ||
128 | return true; | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | } | ||
diff --git a/src/wixcop/Converter.cs b/src/wixcop/Converter.cs new file mode 100644 index 00000000..a204ebe0 --- /dev/null +++ b/src/wixcop/Converter.cs | |||
@@ -0,0 +1,633 @@ | |||
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 WixCop | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Globalization; | ||
8 | using System.IO; | ||
9 | using System.Linq; | ||
10 | using System.Xml; | ||
11 | using System.Xml.Linq; | ||
12 | using WixToolset.Data; | ||
13 | using WixToolset.Extensibility.Services; | ||
14 | using WixToolset.Tools.Core; | ||
15 | |||
16 | /// <summary> | ||
17 | /// WiX source code converter. | ||
18 | /// </summary> | ||
19 | public class Converter | ||
20 | { | ||
21 | private const string XDocumentNewLine = "\n"; // XDocument normlizes "\r\n" to just "\n". | ||
22 | private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; | ||
23 | |||
24 | private static readonly XName FileElementName = WixNamespace + "File"; | ||
25 | private static readonly XName ExePackageElementName = WixNamespace + "ExePackage"; | ||
26 | private static readonly XName MsiPackageElementName = WixNamespace + "MsiPackage"; | ||
27 | private static readonly XName MspPackageElementName = WixNamespace + "MspPackage"; | ||
28 | private static readonly XName MsuPackageElementName = WixNamespace + "MsuPackage"; | ||
29 | private static readonly XName PayloadElementName = WixNamespace + "Payload"; | ||
30 | private static readonly XName WixElementWithoutNamespaceName = XNamespace.None + "Wix"; | ||
31 | |||
32 | private static readonly Dictionary<string, XNamespace> OldToNewNamespaceMapping = new Dictionary<string, XNamespace>() | ||
33 | { | ||
34 | { "http://schemas.microsoft.com/wix/BalExtension", "http://wixtoolset.org/schemas/v4/wxs/bal" }, | ||
35 | { "http://schemas.microsoft.com/wix/ComPlusExtension", "http://wixtoolset.org/schemas/v4/wxs/complus" }, | ||
36 | { "http://schemas.microsoft.com/wix/DependencyExtension", "http://wixtoolset.org/schemas/v4/wxs/dependency" }, | ||
37 | { "http://schemas.microsoft.com/wix/DifxAppExtension", "http://wixtoolset.org/schemas/v4/wxs/difxapp" }, | ||
38 | { "http://schemas.microsoft.com/wix/FirewallExtension", "http://wixtoolset.org/schemas/v4/wxs/firewall" }, | ||
39 | { "http://schemas.microsoft.com/wix/GamingExtension", "http://wixtoolset.org/schemas/v4/wxs/gaming" }, | ||
40 | { "http://schemas.microsoft.com/wix/IIsExtension", "http://wixtoolset.org/schemas/v4/wxs/iis" }, | ||
41 | { "http://schemas.microsoft.com/wix/MsmqExtension", "http://wixtoolset.org/schemas/v4/wxs/msmq" }, | ||
42 | { "http://schemas.microsoft.com/wix/NetFxExtension", "http://wixtoolset.org/schemas/v4/wxs/netfx" }, | ||
43 | { "http://schemas.microsoft.com/wix/PSExtension", "http://wixtoolset.org/schemas/v4/wxs/powershell" }, | ||
44 | { "http://schemas.microsoft.com/wix/SqlExtension", "http://wixtoolset.org/schemas/v4/wxs/sql" }, | ||
45 | { "http://schemas.microsoft.com/wix/TagExtension", "http://wixtoolset.org/schemas/v4/wxs/tag" }, | ||
46 | { "http://schemas.microsoft.com/wix/UtilExtension", "http://wixtoolset.org/schemas/v4/wxs/util" }, | ||
47 | { "http://schemas.microsoft.com/wix/VSExtension", "http://wixtoolset.org/schemas/v4/wxs/vs" }, | ||
48 | { "http://wixtoolset.org/schemas/thmutil/2010", "http://wixtoolset.org/schemas/v4/thmutil" }, | ||
49 | { "http://schemas.microsoft.com/wix/2009/Lux", "http://wixtoolset.org/schemas/v4/lux" }, | ||
50 | { "http://schemas.microsoft.com/wix/2006/wi", "http://wixtoolset.org/schemas/v4/wxs" }, | ||
51 | { "http://schemas.microsoft.com/wix/2006/localization", "http://wixtoolset.org/schemas/v4/wxl" }, | ||
52 | { "http://schemas.microsoft.com/wix/2006/libraries", "http://wixtoolset.org/schemas/v4/wixlib" }, | ||
53 | { "http://schemas.microsoft.com/wix/2006/objects", "http://wixtoolset.org/schemas/v4/wixobj" }, | ||
54 | { "http://schemas.microsoft.com/wix/2006/outputs", "http://wixtoolset.org/schemas/v4/wixout" }, | ||
55 | { "http://schemas.microsoft.com/wix/2007/pdbs", "http://wixtoolset.org/schemas/v4/wixpdb" }, | ||
56 | { "http://schemas.microsoft.com/wix/2003/04/actions", "http://wixtoolset.org/schemas/v4/wi/actions" }, | ||
57 | { "http://schemas.microsoft.com/wix/2006/tables", "http://wixtoolset.org/schemas/v4/wi/tables" }, | ||
58 | { "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" }, | ||
59 | }; | ||
60 | |||
61 | private Dictionary<XName, Action<XElement>> ConvertElementMapping; | ||
62 | |||
63 | /// <summary> | ||
64 | /// Instantiate a new Converter class. | ||
65 | /// </summary> | ||
66 | /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param> | ||
67 | /// <param name="errorsAsWarnings">Test errors to display as warnings.</param> | ||
68 | /// <param name="ignoreErrors">Test errors to ignore.</param> | ||
69 | public Converter(IMessaging messaging, int indentationAmount, IEnumerable<string> errorsAsWarnings = null, IEnumerable<string> ignoreErrors = null) | ||
70 | { | ||
71 | // workaround IDE0009 bug | ||
72 | /*this.ConvertElementMapping = new Dictionary<XName, Action<XElement>>() | ||
73 | { | ||
74 | { Converter.FileElementName, this.ConvertFileElement }, | ||
75 | { Converter.ExePackageElementName, this.ConvertSuppressSignatureValidation }, | ||
76 | { Converter.MsiPackageElementName, this.ConvertSuppressSignatureValidation }, | ||
77 | { Converter.MspPackageElementName, this.ConvertSuppressSignatureValidation }, | ||
78 | { Converter.MsuPackageElementName, this.ConvertSuppressSignatureValidation }, | ||
79 | { Converter.PayloadElementName, this.ConvertSuppressSignatureValidation }, | ||
80 | { Converter.WixElementWithoutNamespaceName, this.ConvertWixElementWithoutNamespace }, | ||
81 | };*/ | ||
82 | this.ConvertElementMapping = new Dictionary<XName, Action<XElement>>(); | ||
83 | this.ConvertElementMapping.Add(Converter.FileElementName, this.ConvertFileElement); | ||
84 | this.ConvertElementMapping.Add(Converter.ExePackageElementName, this.ConvertSuppressSignatureValidation); | ||
85 | this.ConvertElementMapping.Add(Converter.MsiPackageElementName, this.ConvertSuppressSignatureValidation); | ||
86 | this.ConvertElementMapping.Add(Converter.MspPackageElementName, this.ConvertSuppressSignatureValidation); | ||
87 | this.ConvertElementMapping.Add(Converter.MsuPackageElementName, this.ConvertSuppressSignatureValidation); | ||
88 | this.ConvertElementMapping.Add(Converter.PayloadElementName, this.ConvertSuppressSignatureValidation); | ||
89 | this.ConvertElementMapping.Add(Converter.WixElementWithoutNamespaceName, this.ConvertWixElementWithoutNamespace); | ||
90 | |||
91 | this.Messaging = messaging; | ||
92 | |||
93 | this.IndentationAmount = indentationAmount; | ||
94 | |||
95 | this.ErrorsAsWarnings = new HashSet<ConverterTestType>(this.YieldConverterTypes(errorsAsWarnings)); | ||
96 | |||
97 | this.IgnoreErrors = new HashSet<ConverterTestType>(this.YieldConverterTypes(ignoreErrors)); | ||
98 | } | ||
99 | |||
100 | private int Errors { get; set; } | ||
101 | |||
102 | private HashSet<ConverterTestType> ErrorsAsWarnings { get; set; } | ||
103 | |||
104 | private HashSet<ConverterTestType> IgnoreErrors { get; set; } | ||
105 | |||
106 | private IMessaging Messaging { get; } | ||
107 | |||
108 | private int IndentationAmount { get; set; } | ||
109 | |||
110 | private string SourceFile { get; set; } | ||
111 | |||
112 | /// <summary> | ||
113 | /// Convert a file. | ||
114 | /// </summary> | ||
115 | /// <param name="sourceFile">The file to convert.</param> | ||
116 | /// <param name="saveConvertedFile">Option to save the converted errors that are found.</param> | ||
117 | /// <returns>The number of errors found.</returns> | ||
118 | public int ConvertFile(string sourceFile, bool saveConvertedFile) | ||
119 | { | ||
120 | XDocument document; | ||
121 | |||
122 | // Set the instance info. | ||
123 | this.Errors = 0; | ||
124 | this.SourceFile = sourceFile; | ||
125 | |||
126 | try | ||
127 | { | ||
128 | document = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); | ||
129 | } | ||
130 | catch (XmlException e) | ||
131 | { | ||
132 | this.OnError(ConverterTestType.XmlException, (XObject)null, "The xml is invalid. Detail: '{0}'", e.Message); | ||
133 | |||
134 | return this.Errors; | ||
135 | } | ||
136 | |||
137 | this.ConvertDocument(document); | ||
138 | |||
139 | // Fix errors if requested and necessary. | ||
140 | if (saveConvertedFile && 0 < this.Errors) | ||
141 | { | ||
142 | try | ||
143 | { | ||
144 | using (StreamWriter writer = File.CreateText(this.SourceFile)) | ||
145 | { | ||
146 | document.Save(writer, SaveOptions.DisableFormatting | SaveOptions.OmitDuplicateNamespaces); | ||
147 | } | ||
148 | } | ||
149 | catch (UnauthorizedAccessException) | ||
150 | { | ||
151 | this.OnError(ConverterTestType.UnauthorizedAccessException, (XObject)null, "Could not write to file."); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | return this.Errors; | ||
156 | } | ||
157 | |||
158 | /// <summary> | ||
159 | /// Convert a document. | ||
160 | /// </summary> | ||
161 | /// <param name="document">The document to convert.</param> | ||
162 | /// <returns>The number of errors found.</returns> | ||
163 | public int ConvertDocument(XDocument document) | ||
164 | { | ||
165 | XDeclaration declaration = document.Declaration; | ||
166 | |||
167 | // Convert the declaration. | ||
168 | if (null != declaration) | ||
169 | { | ||
170 | if (!String.Equals("utf-8", declaration.Encoding, StringComparison.OrdinalIgnoreCase)) | ||
171 | { | ||
172 | if (this.OnError(ConverterTestType.DeclarationEncodingWrong, document.Root, "The XML declaration encoding is not properly set to 'utf-8'.")) | ||
173 | { | ||
174 | declaration.Encoding = "utf-8"; | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | else // missing declaration | ||
179 | { | ||
180 | if (this.OnError(ConverterTestType.DeclarationMissing, (XNode)null, "This file is missing an XML declaration on the first line.")) | ||
181 | { | ||
182 | document.Declaration = new XDeclaration("1.0", "utf-8", null); | ||
183 | document.Root.AddBeforeSelf(new XText(XDocumentNewLine)); | ||
184 | } | ||
185 | } | ||
186 | |||
187 | // Start converting the nodes at the top. | ||
188 | this.ConvertNode(document.Root, 0); | ||
189 | |||
190 | return this.Errors; | ||
191 | } | ||
192 | |||
193 | /// <summary> | ||
194 | /// Convert a single xml node. | ||
195 | /// </summary> | ||
196 | /// <param name="node">The node to convert.</param> | ||
197 | /// <param name="level">The depth level of the node.</param> | ||
198 | /// <returns>The converted node.</returns> | ||
199 | private void ConvertNode(XNode node, int level) | ||
200 | { | ||
201 | // Convert this node's whitespace. | ||
202 | if ((XmlNodeType.Comment == node.NodeType && 0 > ((XComment)node).Value.IndexOf(XDocumentNewLine, StringComparison.Ordinal)) || | ||
203 | XmlNodeType.CDATA == node.NodeType || XmlNodeType.Element == node.NodeType || XmlNodeType.ProcessingInstruction == node.NodeType) | ||
204 | { | ||
205 | this.ConvertWhitespace(node, level); | ||
206 | } | ||
207 | |||
208 | // Convert this node if it is an element. | ||
209 | XElement element = node as XElement; | ||
210 | |||
211 | if (null != element) | ||
212 | { | ||
213 | this.ConvertElement(element); | ||
214 | |||
215 | // Convert all children of this element. | ||
216 | IEnumerable<XNode> children = element.Nodes().ToList(); | ||
217 | |||
218 | foreach (XNode child in children) | ||
219 | { | ||
220 | this.ConvertNode(child, level + 1); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | |||
225 | private void ConvertElement(XElement element) | ||
226 | { | ||
227 | // Gather any deprecated namespaces, then update this element tree based on those deprecations. | ||
228 | Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces = new Dictionary<XNamespace, XNamespace>(); | ||
229 | |||
230 | foreach (XAttribute declaration in element.Attributes().Where(a => a.IsNamespaceDeclaration)) | ||
231 | { | ||
232 | XNamespace ns; | ||
233 | |||
234 | if (Converter.OldToNewNamespaceMapping.TryGetValue(declaration.Value, out ns)) | ||
235 | { | ||
236 | if (this.OnError(ConverterTestType.XmlnsValueWrong, declaration, "The namespace '{0}' is out of date. It must be '{1}'.", declaration.Value, ns.NamespaceName)) | ||
237 | { | ||
238 | deprecatedToUpdatedNamespaces.Add(declaration.Value, ns); | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | |||
243 | if (deprecatedToUpdatedNamespaces.Any()) | ||
244 | { | ||
245 | Converter.UpdateElementsWithDeprecatedNamespaces(element.DescendantsAndSelf(), deprecatedToUpdatedNamespaces); | ||
246 | } | ||
247 | |||
248 | // Convert the node in much greater detail. | ||
249 | Action<XElement> convert; | ||
250 | |||
251 | if (this.ConvertElementMapping.TryGetValue(element.Name, out convert)) | ||
252 | { | ||
253 | convert(element); | ||
254 | } | ||
255 | } | ||
256 | |||
257 | private void ConvertFileElement(XElement element) | ||
258 | { | ||
259 | if (null == element.Attribute("Id")) | ||
260 | { | ||
261 | XAttribute attribute = element.Attribute("Name"); | ||
262 | |||
263 | if (null == attribute) | ||
264 | { | ||
265 | attribute = element.Attribute("Source"); | ||
266 | } | ||
267 | |||
268 | if (null != attribute) | ||
269 | { | ||
270 | string name = Path.GetFileName(attribute.Value); | ||
271 | |||
272 | if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the default", name)) | ||
273 | { | ||
274 | IEnumerable<XAttribute> attributes = element.Attributes().ToList(); | ||
275 | element.RemoveAttributes(); | ||
276 | element.Add(new XAttribute("Id", ToolsCommon.GetIdentifierFromName(name))); | ||
277 | element.Add(attributes); | ||
278 | } | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | |||
283 | private void ConvertSuppressSignatureValidation(XElement element) | ||
284 | { | ||
285 | XAttribute suppressSignatureValidation = element.Attribute("SuppressSignatureValidation"); | ||
286 | |||
287 | if (null != suppressSignatureValidation) | ||
288 | { | ||
289 | if (this.OnError(ConverterTestType.SuppressSignatureValidationDeprecated, element, "The chain package element contains deprecated '{0}' attribute. Use the 'EnableSignatureValidation' instead.", suppressSignatureValidation)) | ||
290 | { | ||
291 | if ("no" == suppressSignatureValidation.Value) | ||
292 | { | ||
293 | element.Add(new XAttribute("EnableSignatureValidation", "yes")); | ||
294 | } | ||
295 | } | ||
296 | |||
297 | suppressSignatureValidation.Remove(); | ||
298 | } | ||
299 | } | ||
300 | |||
301 | /// <summary> | ||
302 | /// Converts a Wix element. | ||
303 | /// </summary> | ||
304 | /// <param name="element">The Wix element to convert.</param> | ||
305 | /// <returns>The converted element.</returns> | ||
306 | private void ConvertWixElementWithoutNamespace(XElement element) | ||
307 | { | ||
308 | if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName)) | ||
309 | { | ||
310 | element.Name = WixNamespace.GetName(element.Name.LocalName); | ||
311 | |||
312 | element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace. | ||
313 | |||
314 | foreach (XElement elementWithoutNamespace in element.Elements().Where(e => XNamespace.None == e.Name.Namespace)) | ||
315 | { | ||
316 | elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName); | ||
317 | } | ||
318 | } | ||
319 | } | ||
320 | |||
321 | /// <summary> | ||
322 | /// Convert the whitespace adjacent to a node. | ||
323 | /// </summary> | ||
324 | /// <param name="node">The node to convert.</param> | ||
325 | /// <param name="level">The depth level of the node.</param> | ||
326 | private void ConvertWhitespace(XNode node, int level) | ||
327 | { | ||
328 | // Fix the whitespace before this node. | ||
329 | XText whitespace = node.PreviousNode as XText; | ||
330 | |||
331 | if (null != whitespace) | ||
332 | { | ||
333 | if (XmlNodeType.CDATA == node.NodeType) | ||
334 | { | ||
335 | if (this.OnError(ConverterTestType.WhitespacePrecedingCDATAWrong, node, "There should be no whitespace preceding a CDATA node.")) | ||
336 | { | ||
337 | whitespace.Remove(); | ||
338 | } | ||
339 | } | ||
340 | else | ||
341 | { | ||
342 | // TODO: this code complains about whitespace even after the file has been fixed. | ||
343 | if (!Converter.IsLegalWhitespace(this.IndentationAmount, level, whitespace.Value)) | ||
344 | { | ||
345 | if (this.OnError(ConverterTestType.WhitespacePrecedingNodeWrong, node, "The whitespace preceding this node is incorrect.")) | ||
346 | { | ||
347 | Converter.FixWhitespace(this.IndentationAmount, level, whitespace); | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | } | ||
352 | |||
353 | // Fix the whitespace after CDATA nodes. | ||
354 | XCData cdata = node as XCData; | ||
355 | |||
356 | if (null != cdata) | ||
357 | { | ||
358 | whitespace = cdata.NextNode as XText; | ||
359 | |||
360 | if (null != whitespace) | ||
361 | { | ||
362 | if (this.OnError(ConverterTestType.WhitespaceFollowingCDATAWrong, node, "There should be no whitespace following a CDATA node.")) | ||
363 | { | ||
364 | whitespace.Remove(); | ||
365 | } | ||
366 | } | ||
367 | } | ||
368 | else | ||
369 | { | ||
370 | // Fix the whitespace inside and after this node (except for Error which may contain just whitespace). | ||
371 | XElement element = node as XElement; | ||
372 | |||
373 | if (null != element && "Error" != element.Name.LocalName) | ||
374 | { | ||
375 | if (!element.HasElements && !element.IsEmpty && String.IsNullOrEmpty(element.Value.Trim())) | ||
376 | { | ||
377 | if (this.OnError(ConverterTestType.NotEmptyElement, element, "This should be an empty element since it contains nothing but whitespace.")) | ||
378 | { | ||
379 | element.RemoveNodes(); | ||
380 | } | ||
381 | } | ||
382 | |||
383 | whitespace = node.NextNode as XText; | ||
384 | |||
385 | if (null != whitespace) | ||
386 | { | ||
387 | // TODO: this code crashes when level is 0, | ||
388 | // complains about whitespace even after the file has been fixed, | ||
389 | // and the error text doesn't match the error. | ||
390 | if (!Converter.IsLegalWhitespace(this.IndentationAmount, level - 1, whitespace.Value)) | ||
391 | { | ||
392 | if (this.OnError(ConverterTestType.WhitespacePrecedingEndElementWrong, whitespace, "The whitespace preceding this end element is incorrect.")) | ||
393 | { | ||
394 | Converter.FixWhitespace(this.IndentationAmount, level - 1, whitespace); | ||
395 | } | ||
396 | } | ||
397 | } | ||
398 | } | ||
399 | } | ||
400 | } | ||
401 | |||
402 | private IEnumerable<ConverterTestType> YieldConverterTypes(IEnumerable<string> types) | ||
403 | { | ||
404 | if (null != types) | ||
405 | { | ||
406 | foreach (string type in types) | ||
407 | { | ||
408 | ConverterTestType itt; | ||
409 | |||
410 | if (Enum.TryParse<ConverterTestType>(type, true, out itt)) | ||
411 | { | ||
412 | yield return itt; | ||
413 | } | ||
414 | else // not a known ConverterTestType | ||
415 | { | ||
416 | this.OnError(ConverterTestType.ConverterTestTypeUnknown, (XObject)null, "Unknown error type: '{0}'.", type); | ||
417 | } | ||
418 | } | ||
419 | } | ||
420 | } | ||
421 | |||
422 | private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable<XElement> elements, Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces) | ||
423 | { | ||
424 | foreach (XElement element in elements) | ||
425 | { | ||
426 | XNamespace ns; | ||
427 | |||
428 | if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out ns)) | ||
429 | { | ||
430 | element.Name = ns.GetName(element.Name.LocalName); | ||
431 | } | ||
432 | |||
433 | // Remove all the attributes and add them back to with their namespace updated (as necessary). | ||
434 | IEnumerable<XAttribute> attributes = element.Attributes().ToList(); | ||
435 | element.RemoveAttributes(); | ||
436 | |||
437 | foreach (XAttribute attribute in attributes) | ||
438 | { | ||
439 | XAttribute convertedAttribute = attribute; | ||
440 | |||
441 | if (attribute.IsNamespaceDeclaration) | ||
442 | { | ||
443 | if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns)) | ||
444 | { | ||
445 | convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName); | ||
446 | } | ||
447 | } | ||
448 | else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns)) | ||
449 | { | ||
450 | convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value); | ||
451 | } | ||
452 | |||
453 | element.Add(convertedAttribute); | ||
454 | } | ||
455 | } | ||
456 | } | ||
457 | |||
458 | /// <summary> | ||
459 | /// Determine if the whitespace preceding a node is appropriate for its depth level. | ||
460 | /// </summary> | ||
461 | /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param> | ||
462 | /// <param name="level">The depth level that should match this whitespace.</param> | ||
463 | /// <param name="whitespace">The whitespace to validate.</param> | ||
464 | /// <returns>true if the whitespace is legal; false otherwise.</returns> | ||
465 | private static bool IsLegalWhitespace(int indentationAmount, int level, string whitespace) | ||
466 | { | ||
467 | // strip off leading newlines; there can be an arbitrary number of these | ||
468 | while (whitespace.StartsWith(XDocumentNewLine, StringComparison.Ordinal)) | ||
469 | { | ||
470 | whitespace = whitespace.Substring(XDocumentNewLine.Length); | ||
471 | } | ||
472 | |||
473 | // check the length | ||
474 | if (whitespace.Length != level * indentationAmount) | ||
475 | { | ||
476 | return false; | ||
477 | } | ||
478 | |||
479 | // check the spaces | ||
480 | foreach (char character in whitespace) | ||
481 | { | ||
482 | if (' ' != character) | ||
483 | { | ||
484 | return false; | ||
485 | } | ||
486 | } | ||
487 | |||
488 | return true; | ||
489 | } | ||
490 | |||
491 | /// <summary> | ||
492 | /// Fix the whitespace in a Whitespace node. | ||
493 | /// </summary> | ||
494 | /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param> | ||
495 | /// <param name="level">The depth level of the desired whitespace.</param> | ||
496 | /// <param name="whitespace">The whitespace node to fix.</param> | ||
497 | private static void FixWhitespace(int indentationAmount, int level, XText whitespace) | ||
498 | { | ||
499 | int newLineCount = 0; | ||
500 | |||
501 | for (int i = 0; i + 1 < whitespace.Value.Length; ++i) | ||
502 | { | ||
503 | if (XDocumentNewLine == whitespace.Value.Substring(i, 2)) | ||
504 | { | ||
505 | ++i; // skip an extra character | ||
506 | ++newLineCount; | ||
507 | } | ||
508 | } | ||
509 | |||
510 | if (0 == newLineCount) | ||
511 | { | ||
512 | newLineCount = 1; | ||
513 | } | ||
514 | |||
515 | // reset the whitespace value | ||
516 | whitespace.Value = String.Empty; | ||
517 | |||
518 | // add the correct number of newlines | ||
519 | for (int i = 0; i < newLineCount; ++i) | ||
520 | { | ||
521 | whitespace.Value = String.Concat(whitespace.Value, XDocumentNewLine); | ||
522 | } | ||
523 | |||
524 | // add the correct number of spaces based on configured indentation amount | ||
525 | whitespace.Value = String.Concat(whitespace.Value, new string(' ', level * indentationAmount)); | ||
526 | } | ||
527 | |||
528 | /// <summary> | ||
529 | /// Output an error message to the console. | ||
530 | /// </summary> | ||
531 | /// <param name="converterTestType">The type of converter test.</param> | ||
532 | /// <param name="node">The node that caused the error.</param> | ||
533 | /// <param name="message">Detailed error message.</param> | ||
534 | /// <param name="args">Additional formatted string arguments.</param> | ||
535 | /// <returns>Returns true indicating that action should be taken on this error, and false if it should be ignored.</returns> | ||
536 | private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args) | ||
537 | { | ||
538 | if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error | ||
539 | { | ||
540 | return false; | ||
541 | } | ||
542 | |||
543 | // Increase the error count. | ||
544 | this.Errors++; | ||
545 | |||
546 | SourceLineNumber sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber); | ||
547 | bool warning = this.ErrorsAsWarnings.Contains(converterTestType); | ||
548 | string display = String.Format(CultureInfo.CurrentCulture, message, args); | ||
549 | |||
550 | var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString()); | ||
551 | |||
552 | this.Messaging.Write(msg); | ||
553 | |||
554 | return true; | ||
555 | } | ||
556 | |||
557 | /// <summary> | ||
558 | /// Converter test types. These are used to condition error messages down to warnings. | ||
559 | /// </summary> | ||
560 | private enum ConverterTestType | ||
561 | { | ||
562 | /// <summary> | ||
563 | /// Internal-only: displayed when a string cannot be converted to an ConverterTestType. | ||
564 | /// </summary> | ||
565 | ConverterTestTypeUnknown, | ||
566 | |||
567 | /// <summary> | ||
568 | /// Displayed when an XML loading exception has occurred. | ||
569 | /// </summary> | ||
570 | XmlException, | ||
571 | |||
572 | /// <summary> | ||
573 | /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file. | ||
574 | /// </summary> | ||
575 | UnauthorizedAccessException, | ||
576 | |||
577 | /// <summary> | ||
578 | /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'. | ||
579 | /// </summary> | ||
580 | DeclarationEncodingWrong, | ||
581 | |||
582 | /// <summary> | ||
583 | /// Displayed when the XML declaration is missing from the source file. | ||
584 | /// </summary> | ||
585 | DeclarationMissing, | ||
586 | |||
587 | /// <summary> | ||
588 | /// Displayed when the whitespace preceding a CDATA node is wrong. | ||
589 | /// </summary> | ||
590 | WhitespacePrecedingCDATAWrong, | ||
591 | |||
592 | /// <summary> | ||
593 | /// Displayed when the whitespace preceding a node is wrong. | ||
594 | /// </summary> | ||
595 | WhitespacePrecedingNodeWrong, | ||
596 | |||
597 | /// <summary> | ||
598 | /// Displayed when an element is not empty as it should be. | ||
599 | /// </summary> | ||
600 | NotEmptyElement, | ||
601 | |||
602 | /// <summary> | ||
603 | /// Displayed when the whitespace following a CDATA node is wrong. | ||
604 | /// </summary> | ||
605 | WhitespaceFollowingCDATAWrong, | ||
606 | |||
607 | /// <summary> | ||
608 | /// Displayed when the whitespace preceding an end element is wrong. | ||
609 | /// </summary> | ||
610 | WhitespacePrecedingEndElementWrong, | ||
611 | |||
612 | /// <summary> | ||
613 | /// Displayed when the xmlns attribute is missing from the document element. | ||
614 | /// </summary> | ||
615 | XmlnsMissing, | ||
616 | |||
617 | /// <summary> | ||
618 | /// Displayed when the xmlns attribute on the document element is wrong. | ||
619 | /// </summary> | ||
620 | XmlnsValueWrong, | ||
621 | |||
622 | /// <summary> | ||
623 | /// Assign an identifier to a File element when on Id attribute is specified. | ||
624 | /// </summary> | ||
625 | AssignAnonymousFileId, | ||
626 | |||
627 | /// <summary> | ||
628 | /// SuppressSignatureValidation attribute is deprecated and replaced with EnableSignatureValidation. | ||
629 | /// </summary> | ||
630 | SuppressSignatureValidationDeprecated, | ||
631 | } | ||
632 | } | ||
633 | } | ||
diff --git a/src/wixcop/Interfaces/IWixCopCommandLineParser.cs b/src/wixcop/Interfaces/IWixCopCommandLineParser.cs new file mode 100644 index 00000000..2093f5d8 --- /dev/null +++ b/src/wixcop/Interfaces/IWixCopCommandLineParser.cs | |||
@@ -0,0 +1,11 @@ | |||
1 | using WixToolset.Extensibility.Data; | ||
2 | |||
3 | namespace WixCop.Interfaces | ||
4 | { | ||
5 | public interface IWixCopCommandLineParser | ||
6 | { | ||
7 | ICommandLineArguments Arguments { get; set; } | ||
8 | |||
9 | ICommandLineCommand ParseWixCopCommandLine(); | ||
10 | } | ||
11 | } | ||
diff --git a/src/wixcop/Program.cs b/src/wixcop/Program.cs new file mode 100644 index 00000000..b26bd6c9 --- /dev/null +++ b/src/wixcop/Program.cs | |||
@@ -0,0 +1,67 @@ | |||
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 WixCop | ||
4 | { | ||
5 | using System; | ||
6 | using WixCop.CommandLine; | ||
7 | using WixCop.Interfaces; | ||
8 | using WixToolset.Core; | ||
9 | using WixToolset.Extensibility; | ||
10 | using WixToolset.Extensibility.Data; | ||
11 | using WixToolset.Extensibility.Services; | ||
12 | using WixToolset.Tools.Core; | ||
13 | |||
14 | /// <summary> | ||
15 | /// Wix source code style inspector and converter. | ||
16 | /// </summary> | ||
17 | public sealed class Program | ||
18 | { | ||
19 | /// <summary> | ||
20 | /// The main entry point for the application. | ||
21 | /// </summary> | ||
22 | /// <param name="args">The commandline arguments.</param> | ||
23 | /// <returns>The number of errors that were found.</returns> | ||
24 | [STAThread] | ||
25 | public static int Main(string[] args) | ||
26 | { | ||
27 | var serviceProvider = new WixToolsetServiceProvider(); | ||
28 | var listener = new ConsoleMessageListener("WXCP", "wixcop.exe"); | ||
29 | |||
30 | serviceProvider.AddService<IMessageListener>((x, y) => listener); | ||
31 | serviceProvider.AddService<IWixCopCommandLineParser>((x, y) => new WixCopCommandLineParser(x)); | ||
32 | |||
33 | var program = new Program(); | ||
34 | return program.Run(serviceProvider, args); | ||
35 | } | ||
36 | |||
37 | /// <summary> | ||
38 | /// Run the application with the given arguments. | ||
39 | /// </summary> | ||
40 | /// <param name="serviceProvider">Service provider to use throughout this execution.</param> | ||
41 | /// <param name="args">The commandline arguments.</param> | ||
42 | /// <returns>The number of errors that were found.</returns> | ||
43 | public int Run(IServiceProvider serviceProvider, string[] args) | ||
44 | { | ||
45 | try | ||
46 | { | ||
47 | var listener = serviceProvider.GetService<IMessageListener>(); | ||
48 | var messaging = serviceProvider.GetService<IMessaging>(); | ||
49 | messaging.SetListener(listener); | ||
50 | |||
51 | var arguments = serviceProvider.GetService<ICommandLineArguments>(); | ||
52 | arguments.Populate(args); | ||
53 | |||
54 | var commandLine = serviceProvider.GetService<IWixCopCommandLineParser>(); | ||
55 | commandLine.Arguments = arguments; | ||
56 | var command = commandLine.ParseWixCopCommandLine(); | ||
57 | return command?.Execute() ?? 1; | ||
58 | } | ||
59 | catch (Exception e) | ||
60 | { | ||
61 | Console.Error.WriteLine("wixcop.exe : fatal error WXCP0001 : {0}\r\n\n\nStack Trace:\r\n{1}", e.Message, e.StackTrace); | ||
62 | |||
63 | return 1; | ||
64 | } | ||
65 | } | ||
66 | } | ||
67 | } | ||
diff --git a/src/wixcop/WixCop.csproj b/src/wixcop/WixCop.csproj new file mode 100644 index 00000000..9bcae177 --- /dev/null +++ b/src/wixcop/WixCop.csproj | |||
@@ -0,0 +1,32 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- 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. --> | ||
3 | |||
4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
5 | <PropertyGroup> | ||
6 | <TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks> | ||
7 | <OutputType>Exe</OutputType> | ||
8 | <Description>Converter</Description> | ||
9 | <Title>WiX Error Correction Tool</Title> | ||
10 | <DebugType>embedded</DebugType> | ||
11 | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
12 | <!-- <PackAsTool>true</PackAsTool> --> | ||
13 | </PropertyGroup> | ||
14 | |||
15 | <PropertyGroup> | ||
16 | <NoWarn>NU1701</NoWarn> | ||
17 | </PropertyGroup> | ||
18 | |||
19 | <ItemGroup> | ||
20 | <ProjectReference Include="..\WixToolset.Tools.Core\WixToolset.Tools.Core.csproj" /> | ||
21 | </ItemGroup> | ||
22 | |||
23 | <ItemGroup> | ||
24 | <ProjectReference Include="$(WixToolsetRootFolder)\Core\src\WixToolset.Core\WixToolset.Core.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " /> | ||
25 | <PackageReference Include="WixToolset.Core" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core\README.md') " /> | ||
26 | </ItemGroup> | ||
27 | |||
28 | <ItemGroup> | ||
29 | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-63102-01" PrivateAssets="All"/> | ||
30 | <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" /> | ||
31 | </ItemGroup> | ||
32 | </Project> | ||