Migrate WixCop into Tools from wix4.
Sean Hall committed
Sep 2, 2018 at 16:12 UTC
244b46cf7f3252d6dc3884ce184be901d1d173e5
22 files changed
+1921
-69
Tools.sln
+18
@@ -18,6 +18,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
18
.editorconfig = .editorconfig
19
EndProjectSection
20
EndProject
21
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixCop", "src\wixcop\WixCop.csproj", "{2E54120B-8958-40B1-A7FC-851446994CD8}"
22
+EndProject
23
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Tools.Core", "src\WixToolset.Tools.Core\WixToolset.Tools.Core.csproj", "{9C3B486F-AE0E-43BA-823A-30808B73C6B4}"
24
+EndProject
25
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixCopTests", "src\test\wixcop\WixCopTests.csproj", "{F1A8112B-95A1-4AF7-81CB-523BE7DB8E5C}"
26
+EndProject
27
Global
28
GlobalSection(SolutionConfigurationPlatforms) = preSolution
29
Debug|Any CPU = Debug|Any CPU
@@ -44,6 +50,18 @@ Global
50
{0DF5D4CF-8457-469D-8288-13775E984F70}.Debug|Any CPU.Build.0 = Debug|Any CPU
51
{0DF5D4CF-8457-469D-8288-13775E984F70}.Release|Any CPU.ActiveCfg = Release|Any CPU
52
{0DF5D4CF-8457-469D-8288-13775E984F70}.Release|Any CPU.Build.0 = Release|Any CPU
53
+ {2E54120B-8958-40B1-A7FC-851446994CD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54
+ {2E54120B-8958-40B1-A7FC-851446994CD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
55
+ {2E54120B-8958-40B1-A7FC-851446994CD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
56
+ {2E54120B-8958-40B1-A7FC-851446994CD8}.Release|Any CPU.Build.0 = Release|Any CPU
57
+ {9C3B486F-AE0E-43BA-823A-30808B73C6B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58
+ {9C3B486F-AE0E-43BA-823A-30808B73C6B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
59
+ {9C3B486F-AE0E-43BA-823A-30808B73C6B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
60
+ {9C3B486F-AE0E-43BA-823A-30808B73C6B4}.Release|Any CPU.Build.0 = Release|Any CPU
61
+ {F1A8112B-95A1-4AF7-81CB-523BE7DB8E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62
+ {F1A8112B-95A1-4AF7-81CB-523BE7DB8E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
63
+ {F1A8112B-95A1-4AF7-81CB-523BE7DB8E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
64
+ {F1A8112B-95A1-4AF7-81CB-523BE7DB8E5C}.Release|Any CPU.Build.0 = Release|Any CPU
65
EndGlobalSection
66
GlobalSection(SolutionProperties) = preSolution
67
HideSolutionNode = FALSE
src/WixToolset.BuildTasks/ConvertReferences.cs
+2
-5
@@ -3,13 +3,10 @@
3
namespace WixToolset.BuildTasks
4
{
5
using System;
6
- using System.Collections;
6
using System.Collections.Generic;
8
- using System.Globalization;
9
- using System.IO;
10
- using System.Xml;
7
using Microsoft.Build.Framework;
8
using Microsoft.Build.Utilities;
9
+ using WixToolset.Tools.Core;
10
11
/// <summary>
12
/// This task assigns Culture metadata to files based on the value of the Culture attribute on the
@@ -62,7 +59,7 @@ namespace WixToolset.BuildTasks
59
{
60
Dictionary<string, string> newItemMetadeta = new Dictionary<string, string>();
61
65
- if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest)))
62
+ if (!String.IsNullOrEmpty(item.GetMetadata(ToolsCommon.DoNotHarvest)))
63
{
64
continue;
65
}
src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs
+3
-6
@@ -6,19 +6,16 @@ namespace WixToolset.BuildTasks
6
using System.Collections;
7
using System.Globalization;
8
using System.IO;
9
- using System.Text.RegularExpressions;
9
using System.Xml;
10
using Microsoft.Build.Framework;
11
using Microsoft.Build.Utilities;
12
+ using WixToolset.Tools.Core;
13
14
/// <summary>
15
/// This task refreshes the generated file for bundle projects.
16
/// </summary>
17
public class RefreshBundleGeneratedFile : Task
18
{
19
- private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
20
- private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
21
-
19
private ITaskItem[] generatedFiles;
20
private ITaskItem[] projectReferencePaths;
21
@@ -54,14 +51,14 @@ namespace WixToolset.BuildTasks
51
{
52
ITaskItem item = this.ProjectReferencePaths[i];
53
57
- if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest)))
54
+ if (!String.IsNullOrEmpty(item.GetMetadata(ToolsCommon.DoNotHarvest)))
55
{
56
continue;
57
}
58
59
string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i);
60
string projectName = Path.GetFileNameWithoutExtension(projectPath);
64
- string referenceName = Common.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName));
61
+ string referenceName = ToolsCommon.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName));
62
63
string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';');
64
foreach (string pog in pogs)
src/WixToolset.BuildTasks/RefreshGeneratedFile.cs
+3
-6
@@ -6,10 +6,10 @@ namespace WixToolset.BuildTasks
6
using System.Collections;
7
using System.Globalization;
8
using System.IO;
9
- using System.Text.RegularExpressions;
9
using System.Xml;
10
using Microsoft.Build.Framework;
11
using Microsoft.Build.Utilities;
12
+ using WixToolset.Tools.Core;
13
14
/// <summary>
15
/// This task refreshes the generated file that contains ComponentGroupRefs
@@ -17,9 +17,6 @@ namespace WixToolset.BuildTasks
17
/// </summary>
18
public class RefreshGeneratedFile : Task
19
{
20
- private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
21
- private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
22
-
20
private ITaskItem[] generatedFiles;
21
private ITaskItem[] projectReferencePaths;
22
@@ -54,14 +51,14 @@ namespace WixToolset.BuildTasks
51
{
52
ITaskItem item = this.ProjectReferencePaths[i];
53
57
- if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest)))
54
+ if (!String.IsNullOrEmpty(item.GetMetadata(ToolsCommon.DoNotHarvest)))
55
{
56
continue;
57
}
58
59
string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i);
60
string projectName = Path.GetFileNameWithoutExtension(projectPath);
64
- string referenceName = Common.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName));
61
+ string referenceName = ToolsCommon.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName));
62
63
string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';');
64
foreach (string pog in pogs)
src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj
+4
@@ -27,6 +27,10 @@
27
<Content Include="wix.signing.targets" CopyToOutputDirectory="PreserveNewest" />
28
</ItemGroup>
29
30
+ <ItemGroup>
31
+ <ProjectReference Include="..\WixToolset.Tools.Core\WixToolset.Tools.Core.csproj" />
32
+ </ItemGroup>
33
+
34
<ItemGroup>
35
<ProjectReference Include="$(WixToolsetRootFolder)\Core\src\WixToolset.Core\WixToolset.Core.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " />
36
<PackageReference Include="WixToolset.Core" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core\README.md') " />
src/WixToolset.Tools.Core/ConsoleMessageListener.cs
new
+56
@@ -0,0 +1,56 @@
1
+using System;
2
+using System.Globalization;
3
+using System.Text;
4
+using System.Threading;
5
+using WixToolset.Data;
6
+using WixToolset.Extensibility;
7
+
8
+namespace WixToolset.Tools.Core
9
+{
10
+ public sealed class ConsoleMessageListener : IMessageListener
11
+ {
12
+ public ConsoleMessageListener(string shortName, string longName)
13
+ {
14
+ this.ShortAppName = shortName;
15
+ this.LongAppName = longName;
16
+
17
+ PrepareConsoleForLocalization();
18
+ }
19
+
20
+ public string LongAppName { get; }
21
+
22
+ public string ShortAppName { get; }
23
+
24
+ public void Write(Message message)
25
+ {
26
+ var filename = message.SourceLineNumbers?.FileName ?? this.LongAppName;
27
+ var line = message.SourceLineNumbers?.LineNumber ?? -1;
28
+ var type = message.Level.ToString().ToLowerInvariant();
29
+ var output = message.Level >= MessageLevel.Warning ? Console.Out : Console.Error;
30
+
31
+ if (line > 0)
32
+ {
33
+ filename = String.Concat(filename, "(", line, ")");
34
+ }
35
+
36
+ output.WriteLine("{0} : {1} {2}{3:0000}: {4}", filename, type, this.ShortAppName, message.Id, message.ToString());
37
+ }
38
+
39
+ public void Write(string message)
40
+ {
41
+ Console.Out.WriteLine(message);
42
+ }
43
+
44
+ private static void PrepareConsoleForLocalization()
45
+ {
46
+ Thread.CurrentThread.CurrentUICulture = CultureInfo.CurrentUICulture.GetConsoleFallbackUICulture();
47
+
48
+ if (Console.OutputEncoding.CodePage != Encoding.UTF8.CodePage &&
49
+ Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.OEMCodePage &&
50
+ Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.ANSICodePage)
51
+ {
52
+ Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
53
+ }
54
+ }
55
+ }
56
+}
src/WixToolset.Tools.Core/WixToolset.Tools.Core.csproj
new
+28
@@ -0,0 +1,28 @@
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
+ <TargetFramework>netstandard2.0</TargetFramework>
7
+ <Description>Tools Core</Description>
8
+ <Title>WiX Toolset Tools Core</Title>
9
+ <DebugType>embedded</DebugType>
10
+ <PublishRepositoryUrl>true</PublishRepositoryUrl>
11
+ </PropertyGroup>
12
+
13
+ <ItemGroup>
14
+ <ProjectReference Include="$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Data\README.md') " />
15
+ <PackageReference Include="WixToolset.Data" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Data\README.md') " />
16
+
17
+ <ProjectReference Include="$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Extensibility\README.md') " />
18
+ <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Extensibility\README.md') " />
19
+
20
+ <ProjectReference Include="$(WixToolsetRootFolder)\Core\src\WixToolset.Core\WixToolset.Core.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " />
21
+ <PackageReference Include="WixToolset.Core" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core\README.md') " />
22
+ </ItemGroup>
23
+
24
+ <ItemGroup>
25
+ <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-63102-01" PrivateAssets="All"/>
26
+ <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
27
+ </ItemGroup>
28
+</Project>
src/Wixtoolset.Tools.Core/ToolsCommon.cs
renamed
+3
-5
@@ -1,16 +1,14 @@
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
3
+namespace WixToolset.Tools.Core
4
{
5
using System;
6
- using System.Globalization;
7
- using System.Text;
6
using System.Text.RegularExpressions;
7
8
/// <summary>
9
/// Common WixTasks utility methods and types.
10
/// </summary>
13
- internal static class Common
11
+ public static class ToolsCommon
12
{
13
/// <summary>Metadata key name to turn off harvesting of project references.</summary>
14
public const string DoNotHarvest = "DoNotHarvest";
@@ -24,7 +22,7 @@ namespace WixToolset
22
/// <param name="name">File/directory name to generate identifer from</param>
23
/// <returns>A version of the name that is a legal identifier.</returns>
24
/// <remarks>This is duplicated from WiX's Common class.</remarks>
27
- internal static string GetIdentifierFromName(string name)
25
+ public static string GetIdentifierFromName(string name)
26
{
27
string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
28
src/test/wixcop/ConverterFixture.cs
new
+418
@@ -0,0 +1,418 @@
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 WixTest.WixUnitTest
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using System.Text;
8
+ using System.Xml.Linq;
9
+ using WixCop;
10
+ using WixToolset;
11
+ using WixToolset.Data;
12
+ using WixToolset.Extensibility;
13
+ using WixToolset.Extensibility.Services;
14
+ using Xunit;
15
+
16
+ public class ConverterFixture
17
+ {
18
+ private static readonly XNamespace Wix4Namespace = "http://wixtoolset.org/schemas/v4/wxs";
19
+
20
+ [Fact]
21
+ public void EnsuresDeclaration()
22
+ {
23
+ string parse = String.Join(Environment.NewLine,
24
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
25
+ " <Fragment />",
26
+ "</Wix>");
27
+
28
+ string expected = String.Join(Environment.NewLine,
29
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
30
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
31
+ " <Fragment />",
32
+ "</Wix>");
33
+
34
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
35
+
36
+ var messaging = new DummyMessaging();
37
+ Converter converter = new Converter(messaging, 2, null, null);
38
+
39
+ int errors = converter.ConvertDocument(document);
40
+
41
+ string actual = UnformattedDocumentString(document);
42
+
43
+ Assert.Equal(1, errors);
44
+ Assert.Equal(expected, actual);
45
+ }
46
+
47
+ [Fact]
48
+ public void EnsuresUtf8Declaration()
49
+ {
50
+ string parse = String.Join(Environment.NewLine,
51
+ "<?xml version='1.0'?>",
52
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
53
+ " <Fragment />",
54
+ "</Wix>");
55
+
56
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
57
+
58
+ var messaging = new DummyMessaging();
59
+ Converter converter = new Converter(messaging, 4, null, null);
60
+
61
+ int errors = converter.ConvertDocument(document);
62
+
63
+ Assert.Equal(1, errors);
64
+ Assert.Equal("1.0", document.Declaration.Version);
65
+ Assert.Equal("utf-8", document.Declaration.Encoding);
66
+ }
67
+
68
+ [Fact]
69
+ public void CanFixWhitespace()
70
+ {
71
+ string parse = String.Join(Environment.NewLine,
72
+ "<?xml version='1.0' encoding='utf-8'?>",
73
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
74
+ " <Fragment>",
75
+ " <Property Id='Prop'",
76
+ " Value='Val'>",
77
+ " </Property>",
78
+ " </Fragment>",
79
+ "</Wix>");
80
+
81
+ string expected = String.Join(Environment.NewLine,
82
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
83
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
84
+ " <Fragment>",
85
+ " <Property Id=\"Prop\" Value=\"Val\" />",
86
+ " </Fragment>",
87
+ "</Wix>");
88
+
89
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
90
+
91
+ var messaging = new DummyMessaging();
92
+ Converter converter = new Converter(messaging, 4, null, null);
93
+
94
+ int errors = converter.ConvertDocument(document);
95
+
96
+ string actual = UnformattedDocumentString(document);
97
+
98
+ Assert.Equal(4, errors);
99
+ Assert.Equal(expected, actual);
100
+ }
101
+
102
+ [Fact]
103
+ public void CanFixCdataWhitespace()
104
+ {
105
+ string parse = String.Join(Environment.NewLine,
106
+ "<?xml version='1.0' encoding='utf-8'?>",
107
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
108
+ " <Fragment>",
109
+ " <Property Id='Prop'>",
110
+ " <![CDATA[1<2]]>",
111
+ " </Property>",
112
+ " </Fragment>",
113
+ "</Wix>");
114
+
115
+ string expected = String.Join(Environment.NewLine,
116
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
117
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
118
+ " <Fragment>",
119
+ " <Property Id=\"Prop\"><![CDATA[1<2]]></Property>",
120
+ " </Fragment>",
121
+ "</Wix>");
122
+
123
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
124
+
125
+ var messaging = new DummyMessaging();
126
+ Converter converter = new Converter(messaging, 2, null, null);
127
+
128
+ int errors = converter.ConvertDocument(document);
129
+
130
+ string actual = UnformattedDocumentString(document);
131
+
132
+ Assert.Equal(2, errors);
133
+ Assert.Equal(expected, actual);
134
+ }
135
+
136
+ [Fact]
137
+ public void CanConvertMainNamespace()
138
+ {
139
+ string parse = String.Join(Environment.NewLine,
140
+ "<?xml version='1.0' encoding='utf-8'?>",
141
+ "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
142
+ " <Fragment />",
143
+ "</Wix>");
144
+
145
+ string expected = String.Join(Environment.NewLine,
146
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
147
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
148
+ " <Fragment />",
149
+ "</Wix>");
150
+
151
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
152
+
153
+ var messaging = new DummyMessaging();
154
+ Converter converter = new Converter(messaging, 2, null, null);
155
+
156
+ int errors = converter.ConvertDocument(document);
157
+
158
+ string actual = UnformattedDocumentString(document);
159
+
160
+ Assert.Equal(1, errors);
161
+ //Assert.Equal(Wix4Namespace, document.Root.GetDefaultNamespace());
162
+ Assert.Equal(expected, actual);
163
+ }
164
+
165
+ [Fact]
166
+ public void CanConvertNamedMainNamespace()
167
+ {
168
+ string parse = String.Join(Environment.NewLine,
169
+ "<?xml version='1.0' encoding='utf-8'?>",
170
+ "<w:Wix xmlns:w='http://schemas.microsoft.com/wix/2006/wi'>",
171
+ " <w:Fragment />",
172
+ "</w:Wix>");
173
+
174
+ string expected = String.Join(Environment.NewLine,
175
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
176
+ "<w:Wix xmlns:w=\"http://wixtoolset.org/schemas/v4/wxs\">",
177
+ " <w:Fragment />",
178
+ "</w:Wix>");
179
+
180
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
181
+
182
+ var messaging = new DummyMessaging();
183
+ Converter converter = new Converter(messaging, 2, null, null);
184
+
185
+ int errors = converter.ConvertDocument(document);
186
+
187
+ string actual = UnformattedDocumentString(document);
188
+
189
+ Assert.Equal(1, errors);
190
+ Assert.Equal(expected, actual);
191
+ Assert.Equal(Wix4Namespace, document.Root.GetNamespaceOfPrefix("w"));
192
+ }
193
+
194
+ [Fact]
195
+ public void CanConvertNonWixDefaultNamespace()
196
+ {
197
+ string parse = String.Join(Environment.NewLine,
198
+ "<?xml version='1.0' encoding='utf-8'?>",
199
+ "<w:Wix xmlns:w='http://schemas.microsoft.com/wix/2006/wi' xmlns='http://schemas.microsoft.com/wix/UtilExtension'>",
200
+ " <w:Fragment>",
201
+ " <Test />",
202
+ " </w:Fragment>",
203
+ "</w:Wix>");
204
+
205
+ string expected = String.Join(Environment.NewLine,
206
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
207
+ "<w:Wix xmlns:w=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
208
+ " <w:Fragment>",
209
+ " <Test />",
210
+ " </w:Fragment>",
211
+ "</w:Wix>");
212
+
213
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
214
+
215
+ var messaging = new DummyMessaging();
216
+ Converter converter = new Converter(messaging, 2, null, null);
217
+
218
+ int errors = converter.ConvertDocument(document);
219
+
220
+ string actual = UnformattedDocumentString(document);
221
+
222
+ Assert.Equal(2, errors);
223
+ Assert.Equal(expected, actual);
224
+ Assert.Equal(Wix4Namespace, document.Root.GetNamespaceOfPrefix("w"));
225
+ Assert.Equal("http://wixtoolset.org/schemas/v4/wxs/util", document.Root.GetDefaultNamespace());
226
+ }
227
+
228
+ [Fact]
229
+ public void CanConvertExtensionNamespace()
230
+ {
231
+ string parse = String.Join(Environment.NewLine,
232
+ "<?xml version='1.0' encoding='utf-8'?>",
233
+ "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>",
234
+ " <Fragment />",
235
+ "</Wix>");
236
+
237
+ string expected = String.Join(Environment.NewLine,
238
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
239
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:util=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
240
+ " <Fragment />",
241
+ "</Wix>");
242
+
243
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
244
+
245
+ var messaging = new DummyMessaging();
246
+ Converter converter = new Converter(messaging, 2, null, null);
247
+
248
+ int errors = converter.ConvertDocument(document);
249
+
250
+ string actual = UnformattedDocumentString(document);
251
+
252
+ Assert.Equal(2, errors);
253
+ Assert.Equal(expected, actual);
254
+ Assert.Equal(Wix4Namespace, document.Root.GetDefaultNamespace());
255
+ }
256
+
257
+ [Fact]
258
+ public void CanConvertMissingNamespace()
259
+ {
260
+ string parse = String.Join(Environment.NewLine,
261
+ "<?xml version='1.0' encoding='utf-8'?>",
262
+ "<Wix>",
263
+ " <Fragment />",
264
+ "</Wix>");
265
+
266
+ string expected = String.Join(Environment.NewLine,
267
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
268
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
269
+ " <Fragment />",
270
+ "</Wix>");
271
+
272
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
273
+
274
+ var messaging = new DummyMessaging();
275
+ Converter converter = new Converter(messaging, 2, null, null);
276
+
277
+ int errors = converter.ConvertDocument(document);
278
+
279
+ string actual = UnformattedDocumentString(document);
280
+
281
+ Assert.Equal(1, errors);
282
+ Assert.Equal(expected, actual);
283
+ Assert.Equal(Wix4Namespace, document.Root.GetDefaultNamespace());
284
+ }
285
+
286
+ [Fact]
287
+ public void CanConvertAnonymousFile()
288
+ {
289
+ string parse = String.Join(Environment.NewLine,
290
+ "<?xml version='1.0' encoding='utf-8'?>",
291
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
292
+ " <File Source='path\\to\\foo.txt' />",
293
+ "</Wix>");
294
+
295
+ string expected = String.Join(Environment.NewLine,
296
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
297
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
298
+ " <File Id=\"foo.txt\" Source=\"path\\to\\foo.txt\" />",
299
+ "</Wix>");
300
+
301
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
302
+
303
+ var messaging = new DummyMessaging();
304
+ Converter converter = new Converter(messaging, 2, null, null);
305
+
306
+ int errors = converter.ConvertDocument(document);
307
+
308
+ string actual = UnformattedDocumentString(document);
309
+
310
+ Assert.Equal(1, errors);
311
+ Assert.Equal(expected, actual);
312
+ }
313
+
314
+ [Fact]
315
+ public void CanConvertSuppressSignatureValidationNo()
316
+ {
317
+ string parse = String.Join(Environment.NewLine,
318
+ "<?xml version='1.0' encoding='utf-8'?>",
319
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
320
+ " <MsiPackage SuppressSignatureValidation='no' />",
321
+ "</Wix>");
322
+
323
+ string expected = String.Join(Environment.NewLine,
324
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
325
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
326
+ " <MsiPackage EnableSignatureValidation=\"yes\" />",
327
+ "</Wix>");
328
+
329
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
330
+
331
+ var messaging = new DummyMessaging();
332
+ Converter converter = new Converter(messaging, 2, null, null);
333
+
334
+ int errors = converter.ConvertDocument(document);
335
+
336
+ string actual = UnformattedDocumentString(document);
337
+
338
+ Assert.Equal(1, errors);
339
+ Assert.Equal(expected, actual);
340
+ }
341
+
342
+ [Fact]
343
+ public void CanConvertSuppressSignatureValidationYes()
344
+ {
345
+ string parse = String.Join(Environment.NewLine,
346
+ "<?xml version='1.0' encoding='utf-8'?>",
347
+ "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
348
+ " <Payload SuppressSignatureValidation='yes' />",
349
+ "</Wix>");
350
+
351
+ string expected = String.Join(Environment.NewLine,
352
+ "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
353
+ "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
354
+ " <Payload />",
355
+ "</Wix>");
356
+
357
+ XDocument document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
358
+
359
+ var messaging = new DummyMessaging();
360
+ Converter converter = new Converter(messaging, 2, null, null);
361
+
362
+ int errors = converter.ConvertDocument(document);
363
+
364
+ string actual = UnformattedDocumentString(document);
365
+
366
+ Assert.Equal(1, errors);
367
+ Assert.Equal(expected, actual);
368
+ }
369
+
370
+ private static string UnformattedDocumentString(XDocument document)
371
+ {
372
+ StringBuilder sb = new StringBuilder();
373
+
374
+ using (StringWriter writer = new StringWriter(sb))
375
+ {
376
+ document.Save(writer, SaveOptions.DisableFormatting | SaveOptions.OmitDuplicateNamespaces);
377
+ }
378
+
379
+ return sb.ToString();
380
+ }
381
+
382
+ private class DummyMessaging : IMessaging
383
+ {
384
+ public bool EncounteredError { get; set; }
385
+
386
+ public int LastErrorNumber { get; set; }
387
+
388
+ public bool ShowVerboseMessages { get; set; }
389
+ public bool SuppressAllWarnings { get; set; }
390
+ public bool WarningsAsError { get; set; }
391
+
392
+ public void ElevateWarningMessage(int warningNumber)
393
+ {
394
+ }
395
+
396
+ public string FormatMessage(Message message)
397
+ {
398
+ return "";
399
+ }
400
+
401
+ public void SetListener(IMessageListener listener)
402
+ {
403
+ }
404
+
405
+ public void SuppressWarningMessage(int warningNumber)
406
+ {
407
+ }
408
+
409
+ public void Write(Message message)
410
+ {
411
+ }
412
+
413
+ public void Write(string message, bool verbose = false)
414
+ {
415
+ }
416
+ }
417
+ }
418
+}
src/test/wixcop/TestData/SingleFile/ConvertedSingleFile.wxs
new
+60
@@ -0,0 +1,60 @@
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
+
5
+
6
+<?include WixVer.wxi ?>
7
+
8
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:swid="http://wixtoolset.org/schemas/v4/wxs/tag" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
9
+ <Product Id="*" Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)" Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D">
10
+ <Package Compressed="yes" InstallerVersion="200" SummaryCodepage="1252" InstallScope="perMachine" />
11
+ <swid:Tag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
12
+
13
+ <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
14
+
15
+ <MediaTemplate CabinetTemplate="core{0}.cab" />
16
+
17
+ <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
18
+ <Component Id="Licensing" Directory="INSTALLFOLDER">
19
+ <File Id="LICENSE.TXT" Source="LICENSE.TXT" />
20
+ </Component>
21
+
22
+ <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
23
+ <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
24
+ <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
25
+ </RegistryKey>
26
+ </Component>
27
+
28
+ <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
29
+ <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
30
+ <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
31
+ </RegistryKey>
32
+ </Component>
33
+
34
+ <Component Id="ProductInformation" Directory="BinFolder">
35
+ <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
36
+ <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string" />
37
+ <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
38
+ </RegistryKey>
39
+
40
+ <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
41
+ </Component>
42
+
43
+ <Component Directory="BinFolder">
44
+ <File Id="wixtoolset.org.ico" Source="common\wixtoolset.org.ico" />
45
+ <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
46
+ </Component>
47
+
48
+ <ComponentGroupRef Id="ToolsetComponents" />
49
+ <ComponentGroupRef Id="ExtensionComponents" />
50
+ <ComponentGroupRef Id="LuxComponents" />
51
+ <ComponentGroupRef Id="DocComponents" />
52
+ </Feature>
53
+
54
+ <FeatureRef Id="Feature_MSBuild" />
55
+ <FeatureRef Id="Feature_Intellisense2010" />
56
+ <FeatureRef Id="Feature_Intellisense2012" />
57
+ <FeatureRef Id="Feature_Intellisense2013" />
58
+ <FeatureRef Id="Feature_Intellisense2015" />
59
+ </Product>
60
+</Wix>
src/test/wixcop/TestData/SingleFile/SingleFile.wxs
new
+61
@@ -0,0 +1,61 @@
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
+
5
+
6
+<?include WixVer.wxi ?>
7
+
8
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:swid="http://schemas.microsoft.com/wix/TagExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
9
+ <Product Id="*" Name="!(loc.ShortProduct) v$(var.WixMajorMinor) Core" Language="1033" Manufacturer="!(loc.Company)"
10
+ Version="$(var.WixMsiProductVersion)" UpgradeCode="3618724B-2523-44F9-A908-866AA619504D">
11
+ <Package Compressed="yes" InstallerVersion="200" SummaryCodepage="1252" InstallScope="perMachine" />
12
+ <swid:Tag Regid="!(loc.Regid)" InstallDirectory="INSTALLFOLDER" />
13
+
14
+ <MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed." />
15
+
16
+ <MediaTemplate CabinetTemplate="core{0}.cab" />
17
+
18
+ <Feature Id="Feature_WiX" Title="WiX Toolset" Level="1">
19
+ <Component Id="Licensing" Directory="INSTALLFOLDER">
20
+ <File Source="LICENSE.TXT" />
21
+ </Component>
22
+
23
+ <Component Id="ProductRegistration" Directory="INSTALLFOLDER">
24
+ <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
25
+ <RegistryValue Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
26
+ </RegistryKey>
27
+ </Component>
28
+
29
+ <Component Id="ProductFamilyRegistration" Directory="INSTALLFOLDER">
30
+ <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajor).x">
31
+ <RegistryValue Name="v$(var.WixMajorMinor)" Value="[INSTALLFOLDER]" Type="string" />
32
+ </RegistryKey>
33
+ </Component>
34
+
35
+ <Component Id="ProductInformation" Directory="BinFolder">
36
+ <RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\Windows Installer XML\$(var.WixMajorMinor)">
37
+ <RegistryValue Name="InstallRoot" Value="[BinFolder]" Type="string"/>
38
+ <RegistryValue Name="ProductVersion" Value="[ProductVersion]" Type="string" />
39
+ </RegistryKey>
40
+
41
+ <RemoveFolder Id="CleanupShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
42
+ </Component>
43
+
44
+ <Component Directory="BinFolder">
45
+ <File Source="common\wixtoolset.org.ico" />
46
+ <util:InternetShortcut Id="wixtoolset.org" Directory="ShortcutFolder" Name="WiX Home Page" Target="http://wixtoolset.org/" IconFile="file://[#wixtoolset.org.ico]" />
47
+ </Component>
48
+
49
+ <ComponentGroupRef Id="ToolsetComponents" />
50
+ <ComponentGroupRef Id="ExtensionComponents" />
51
+ <ComponentGroupRef Id="LuxComponents" />
52
+ <ComponentGroupRef Id="DocComponents" />
53
+ </Feature>
54
+
55
+ <FeatureRef Id="Feature_MSBuild" />
56
+ <FeatureRef Id="Feature_Intellisense2010" />
57
+ <FeatureRef Id="Feature_Intellisense2012" />
58
+ <FeatureRef Id="Feature_Intellisense2013" />
59
+ <FeatureRef Id="Feature_Intellisense2015" />
60
+ </Product>
61
+</Wix>
src/test/wixcop/WixCopFixture.cs
new
+107
@@ -0,0 +1,107 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.IO;
4
+using System.Linq;
5
+using WixBuildTools.TestSupport;
6
+using WixCop.CommandLine;
7
+using WixCop.Interfaces;
8
+using WixToolset.Core;
9
+using WixToolset.Core.TestPackage;
10
+using WixToolset.Extensibility;
11
+using WixToolset.Extensibility.Services;
12
+using Xunit;
13
+
14
+namespace WixCopTests
15
+{
16
+ public class WixCopFixture
17
+ {
18
+ [Fact]
19
+ public void CanConvertSingleFile()
20
+ {
21
+ const string beforeFileName = "SingleFile.wxs";
22
+ const string afterFileName = "ConvertedSingleFile.wxs";
23
+ var folder = TestData.Get(@"TestData\SingleFile");
24
+
25
+ using (var fs = new DisposableFileSystem())
26
+ {
27
+ var baseFolder = fs.GetFolder(true);
28
+ var targetFile = Path.Combine(baseFolder, beforeFileName);
29
+ File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName));
30
+
31
+ var runner = new WixCopRunner
32
+ {
33
+ FixErrors = true,
34
+ SearchPatterns =
35
+ {
36
+ targetFile,
37
+ },
38
+ };
39
+
40
+ var result = runner.Execute(out var messages);
41
+
42
+ Assert.Equal(2, result);
43
+
44
+ var actualLines = File.ReadAllLines(targetFile);
45
+ var expectedLines = File.ReadAllLines(Path.Combine(folder, afterFileName));
46
+ Assert.Equal(expectedLines, actualLines);
47
+
48
+ var runner2 = new WixCopRunner
49
+ {
50
+ FixErrors = true,
51
+ SearchPatterns =
52
+ {
53
+ targetFile,
54
+ },
55
+ };
56
+
57
+ var result2 = runner2.Execute(out var messages2);
58
+
59
+ Assert.Equal(0, result2);
60
+ }
61
+ }
62
+
63
+ private class WixCopRunner
64
+ {
65
+ public bool FixErrors { get; set; }
66
+
67
+ public List<string> SearchPatterns { get; } = new List<string>();
68
+
69
+ public int Execute(out List<string> messages)
70
+ {
71
+ var argList = new List<string>();
72
+ if (this.FixErrors)
73
+ {
74
+ argList.Add("-f");
75
+ }
76
+
77
+ foreach (string searchPattern in this.SearchPatterns)
78
+ {
79
+ argList.Add(searchPattern);
80
+ }
81
+
82
+ return WixCopRunner.Execute(argList.ToArray(), out messages);
83
+ }
84
+
85
+ public static int Execute(string[] args, out List<string> messages)
86
+ {
87
+ var listener = new TestMessageListener();
88
+
89
+ var serviceProvider = new WixToolsetServiceProvider();
90
+ serviceProvider.AddService<IMessageListener>((x, y) => listener);
91
+ serviceProvider.AddService<IWixCopCommandLineParser>((x, y) => new WixCopCommandLineParser(x));
92
+
93
+ var result = Execute(serviceProvider, args);
94
+
95
+ var messaging = serviceProvider.GetService<IMessaging>();
96
+ messages = listener.Messages.Select(x => messaging.FormatMessage(x)).ToList();
97
+ return result;
98
+ }
99
+
100
+ public static int Execute(IServiceProvider serviceProvider, string[] args)
101
+ {
102
+ var wixcop = new WixCop.Program();
103
+ return wixcop.Run(serviceProvider, args);
104
+ }
105
+ }
106
+ }
107
+}
src/test/wixcop/WixCopTests.csproj
new
+41
@@ -0,0 +1,41 @@
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
+ <TargetFramework>net461</TargetFramework>
7
+ <IsPackable>false</IsPackable>
8
+ <DebugType>embedded</DebugType>
9
+ </PropertyGroup>
10
+ <ItemGroup>
11
+ <None Remove="TestData\SingleFile\ConvertedSingleFile.wxs" />
12
+ <None Remove="TestData\SingleFile\SingleFile.wxs" />
13
+ </ItemGroup>
14
+ <ItemGroup>
15
+ <Content Include="TestData\SingleFile\ConvertedSingleFile.wxs">
16
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
17
+ </Content>
18
+ <Content Include="TestData\SingleFile\SingleFile.wxs">
19
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
20
+ </Content>
21
+ </ItemGroup>
22
+
23
+ <ItemGroup>
24
+ <ProjectReference Include="..\..\wixcop\WixCop.csproj" />
25
+ </ItemGroup>
26
+
27
+ <ItemGroup>
28
+ <ProjectReference Include="$(WixToolsetRootFolder)\Core\src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " />
29
+ <PackageReference Include="WixToolset.Core.TestPackage" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core\README.md') " />
30
+ </ItemGroup>
31
+
32
+ <ItemGroup>
33
+ <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" />
34
+ </ItemGroup>
35
+
36
+ <ItemGroup>
37
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
38
+ <PackageReference Include="xunit" Version="2.4.0" />
39
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
40
+ </ItemGroup>
41
+</Project>
src/wix/Program.cs
+1
-47
@@ -13,6 +13,7 @@ namespace WixToolset.Tools
13
using WixToolset.Extensibility;
14
using WixToolset.Extensibility.Data;
15
using WixToolset.Extensibility.Services;
16
+ using WixToolset.Tools.Core;
17
18
/// <summary>
19
/// Wix Toolset Command-Line Interface.
@@ -79,52 +80,5 @@ namespace WixToolset.Tools
80
81
return extensionManager;
82
}
82
-
83
- private class ConsoleMessageListener : IMessageListener
84
- {
85
- public ConsoleMessageListener(string shortName, string longName)
86
- {
87
- this.ShortAppName = shortName;
88
- this.LongAppName = longName;
89
-
90
- PrepareConsoleForLocalization();
91
- }
92
-
93
- public string LongAppName { get; }
94
-
95
- public string ShortAppName { get; }
96
-
97
- public void Write(Message message)
98
- {
99
- var filename = message.SourceLineNumbers?.FileName ?? this.LongAppName;
100
- var line = message.SourceLineNumbers?.LineNumber ?? -1;
101
- var type = message.Level.ToString().ToLowerInvariant();
102
- var output = message.Level >= MessageLevel.Warning ? Console.Out : Console.Error;
103
-
104
- if (line > 0)
105
- {
106
- filename = String.Concat(filename, "(", line, ")");
107
- }
108
-
109
- output.WriteLine("{0} : {1} {2}{3:0000}: {4}", filename, type, this.ShortAppName, message.Id, message.ToString());
110
- }
111
-
112
- public void Write(string message)
113
- {
114
- Console.Out.WriteLine(message);
115
- }
116
-
117
- private static void PrepareConsoleForLocalization()
118
- {
119
- Thread.CurrentThread.CurrentUICulture = CultureInfo.CurrentUICulture.GetConsoleFallbackUICulture();
120
-
121
- if (Console.OutputEncoding.CodePage != Encoding.UTF8.CodePage &&
122
- Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.OEMCodePage &&
123
- Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.ANSICodePage)
124
- {
125
- Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
126
- }
127
- }
128
- }
83
}
84
}
src/wix/wix.csproj
+4
@@ -16,6 +16,10 @@
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') " />
src/wixcop/CommandLine/ConvertCommand.cs
new
+212
@@ -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
+}
src/wixcop/CommandLine/HelpCommand.cs
new
+25
@@ -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
+}
src/wixcop/CommandLine/WixCopCommandLineParser.cs
new
+132
@@ -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
+}
src/wixcop/Converter.cs
new
+633
@@ -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
+}
src/wixcop/Interfaces/IWixCopCommandLineParser.cs
new
+11
@@ -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
+}
src/wixcop/Program.cs
new
+67
@@ -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
+}
src/wixcop/WixCop.csproj
new
+32
@@ -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>