aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2018-09-02 16:12:29 -0500
committerSean Hall <r.sean.hall@gmail.com>2018-09-13 12:05:57 -0500
commit244b46cf7f3252d6dc3884ce184be901d1d173e5 (patch)
treebd6fb4349b926001138d1a3415f93370d64e538f
parent026d0af96fac5cd2d3d84ade657949ddc7144b99 (diff)
downloadwix-244b46cf7f3252d6dc3884ce184be901d1d173e5.tar.gz
wix-244b46cf7f3252d6dc3884ce184be901d1d173e5.tar.bz2
wix-244b46cf7f3252d6dc3884ce184be901d1d173e5.zip
Migrate WixCop into Tools from wix4.
-rw-r--r--Tools.sln18
-rw-r--r--src/WixToolset.BuildTasks/ConvertReferences.cs7
-rw-r--r--src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs9
-rw-r--r--src/WixToolset.BuildTasks/RefreshGeneratedFile.cs9
-rw-r--r--src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj4
-rw-r--r--src/WixToolset.Tools.Core/ConsoleMessageListener.cs56
-rw-r--r--src/WixToolset.Tools.Core/WixToolset.Tools.Core.csproj28
-rw-r--r--src/Wixtoolset.Tools.Core/ToolsCommon.cs (renamed from src/WixToolset.BuildTasks/Common.cs)8
-rw-r--r--src/test/wixcop/ConverterFixture.cs418
-rw-r--r--src/test/wixcop/TestData/SingleFile/ConvertedSingleFile.wxs60
-rw-r--r--src/test/wixcop/TestData/SingleFile/SingleFile.wxs61
-rw-r--r--src/test/wixcop/WixCopFixture.cs107
-rw-r--r--src/test/wixcop/WixCopTests.csproj41
-rw-r--r--src/wix/Program.cs48
-rw-r--r--src/wix/wix.csproj4
-rw-r--r--src/wixcop/CommandLine/ConvertCommand.cs212
-rw-r--r--src/wixcop/CommandLine/HelpCommand.cs25
-rw-r--r--src/wixcop/CommandLine/WixCopCommandLineParser.cs132
-rw-r--r--src/wixcop/Converter.cs633
-rw-r--r--src/wixcop/Interfaces/IWixCopCommandLineParser.cs11
-rw-r--r--src/wixcop/Program.cs67
-rw-r--r--src/wixcop/WixCop.csproj32
22 files changed, 1921 insertions, 69 deletions
diff --git a/Tools.sln b/Tools.sln
index 63c7ea4e..ca2e23d6 100644
--- a/Tools.sln
+++ b/Tools.sln
@@ -18,6 +18,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
18 .editorconfig = .editorconfig 18 .editorconfig = .editorconfig
19 EndProjectSection 19 EndProjectSection
20EndProject 20EndProject
21Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixCop", "src\wixcop\WixCop.csproj", "{2E54120B-8958-40B1-A7FC-851446994CD8}"
22EndProject
23Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Tools.Core", "src\WixToolset.Tools.Core\WixToolset.Tools.Core.csproj", "{9C3B486F-AE0E-43BA-823A-30808B73C6B4}"
24EndProject
25Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixCopTests", "src\test\wixcop\WixCopTests.csproj", "{F1A8112B-95A1-4AF7-81CB-523BE7DB8E5C}"
26EndProject
21Global 27Global
22 GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 GlobalSection(SolutionConfigurationPlatforms) = preSolution
23 Debug|Any CPU = Debug|Any CPU 29 Debug|Any CPU = Debug|Any CPU
@@ -44,6 +50,18 @@ Global
44 {0DF5D4CF-8457-469D-8288-13775E984F70}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 {0DF5D4CF-8457-469D-8288-13775E984F70}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 {0DF5D4CF-8457-469D-8288-13775E984F70}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 {0DF5D4CF-8457-469D-8288-13775E984F70}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 {0DF5D4CF-8457-469D-8288-13775E984F70}.Release|Any CPU.Build.0 = 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
47 EndGlobalSection 65 EndGlobalSection
48 GlobalSection(SolutionProperties) = preSolution 66 GlobalSection(SolutionProperties) = preSolution
49 HideSolutionNode = FALSE 67 HideSolutionNode = FALSE
diff --git a/src/WixToolset.BuildTasks/ConvertReferences.cs b/src/WixToolset.BuildTasks/ConvertReferences.cs
index fe137633..ef50c918 100644
--- a/src/WixToolset.BuildTasks/ConvertReferences.cs
+++ b/src/WixToolset.BuildTasks/ConvertReferences.cs
@@ -3,13 +3,10 @@
3namespace WixToolset.BuildTasks 3namespace WixToolset.BuildTasks
4{ 4{
5 using System; 5 using System;
6 using System.Collections;
7 using System.Collections.Generic; 6 using System.Collections.Generic;
8 using System.Globalization;
9 using System.IO;
10 using System.Xml;
11 using Microsoft.Build.Framework; 7 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities; 8 using Microsoft.Build.Utilities;
9 using WixToolset.Tools.Core;
13 10
14 /// <summary> 11 /// <summary>
15 /// This task assigns Culture metadata to files based on the value of the Culture attribute on the 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
62 { 59 {
63 Dictionary<string, string> newItemMetadeta = new Dictionary<string, string>(); 60 Dictionary<string, string> newItemMetadeta = new Dictionary<string, string>();
64 61
65 if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest))) 62 if (!String.IsNullOrEmpty(item.GetMetadata(ToolsCommon.DoNotHarvest)))
66 { 63 {
67 continue; 64 continue;
68 } 65 }
diff --git a/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs b/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs
index 5445e0cd..80305f59 100644
--- a/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs
+++ b/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs
@@ -6,19 +6,16 @@ namespace WixToolset.BuildTasks
6 using System.Collections; 6 using System.Collections;
7 using System.Globalization; 7 using System.Globalization;
8 using System.IO; 8 using System.IO;
9 using System.Text.RegularExpressions;
10 using System.Xml; 9 using System.Xml;
11 using Microsoft.Build.Framework; 10 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities; 11 using Microsoft.Build.Utilities;
12 using WixToolset.Tools.Core;
13 13
14 /// <summary> 14 /// <summary>
15 /// This task refreshes the generated file for bundle projects. 15 /// This task refreshes the generated file for bundle projects.
16 /// </summary> 16 /// </summary>
17 public class RefreshBundleGeneratedFile : Task 17 public class RefreshBundleGeneratedFile : Task
18 { 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
22 private ITaskItem[] generatedFiles; 19 private ITaskItem[] generatedFiles;
23 private ITaskItem[] projectReferencePaths; 20 private ITaskItem[] projectReferencePaths;
24 21
@@ -54,14 +51,14 @@ namespace WixToolset.BuildTasks
54 { 51 {
55 ITaskItem item = this.ProjectReferencePaths[i]; 52 ITaskItem item = this.ProjectReferencePaths[i];
56 53
57 if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest))) 54 if (!String.IsNullOrEmpty(item.GetMetadata(ToolsCommon.DoNotHarvest)))
58 { 55 {
59 continue; 56 continue;
60 } 57 }
61 58
62 string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i); 59 string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i);
63 string projectName = Path.GetFileNameWithoutExtension(projectPath); 60 string projectName = Path.GetFileNameWithoutExtension(projectPath);
64 string referenceName = Common.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName)); 61 string referenceName = ToolsCommon.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName));
65 62
66 string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';'); 63 string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';');
67 foreach (string pog in pogs) 64 foreach (string pog in pogs)
diff --git a/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs b/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs
index fdfc4774..101b5363 100644
--- a/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs
+++ b/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs
@@ -6,10 +6,10 @@ namespace WixToolset.BuildTasks
6 using System.Collections; 6 using System.Collections;
7 using System.Globalization; 7 using System.Globalization;
8 using System.IO; 8 using System.IO;
9 using System.Text.RegularExpressions;
10 using System.Xml; 9 using System.Xml;
11 using Microsoft.Build.Framework; 10 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities; 11 using Microsoft.Build.Utilities;
12 using WixToolset.Tools.Core;
13 13
14 /// <summary> 14 /// <summary>
15 /// This task refreshes the generated file that contains ComponentGroupRefs 15 /// This task refreshes the generated file that contains ComponentGroupRefs
@@ -17,9 +17,6 @@ namespace WixToolset.BuildTasks
17 /// </summary> 17 /// </summary>
18 public class RefreshGeneratedFile : Task 18 public class RefreshGeneratedFile : Task
19 { 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
23 private ITaskItem[] generatedFiles; 20 private ITaskItem[] generatedFiles;
24 private ITaskItem[] projectReferencePaths; 21 private ITaskItem[] projectReferencePaths;
25 22
@@ -54,14 +51,14 @@ namespace WixToolset.BuildTasks
54 { 51 {
55 ITaskItem item = this.ProjectReferencePaths[i]; 52 ITaskItem item = this.ProjectReferencePaths[i];
56 53
57 if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest))) 54 if (!String.IsNullOrEmpty(item.GetMetadata(ToolsCommon.DoNotHarvest)))
58 { 55 {
59 continue; 56 continue;
60 } 57 }
61 58
62 string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i); 59 string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i);
63 string projectName = Path.GetFileNameWithoutExtension(projectPath); 60 string projectName = Path.GetFileNameWithoutExtension(projectPath);
64 string referenceName = Common.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName)); 61 string referenceName = ToolsCommon.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName));
65 62
66 string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';'); 63 string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';');
67 foreach (string pog in pogs) 64 foreach (string pog in pogs)
diff --git a/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj
index 8a5c388d..39c8824c 100644
--- a/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj
+++ b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj
@@ -28,6 +28,10 @@
28 </ItemGroup> 28 </ItemGroup>
29 29
30 <ItemGroup> 30 <ItemGroup>
31 <ProjectReference Include="..\WixToolset.Tools.Core\WixToolset.Tools.Core.csproj" />
32 </ItemGroup>
33
34 <ItemGroup>
31 <ProjectReference Include="$(WixToolsetRootFolder)\Core\src\WixToolset.Core\WixToolset.Core.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " /> 35 <ProjectReference Include="$(WixToolsetRootFolder)\Core\src\WixToolset.Core\WixToolset.Core.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " />
32 <PackageReference Include="WixToolset.Core" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core\README.md') " /> 36 <PackageReference Include="WixToolset.Core" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core\README.md') " />
33 37
diff --git a/src/WixToolset.Tools.Core/ConsoleMessageListener.cs b/src/WixToolset.Tools.Core/ConsoleMessageListener.cs
new file mode 100644
index 00000000..5b1fb988
--- /dev/null
+++ b/src/WixToolset.Tools.Core/ConsoleMessageListener.cs
@@ -0,0 +1,56 @@
1using System;
2using System.Globalization;
3using System.Text;
4using System.Threading;
5using WixToolset.Data;
6using WixToolset.Extensibility;
7
8namespace 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}
diff --git a/src/WixToolset.Tools.Core/WixToolset.Tools.Core.csproj b/src/WixToolset.Tools.Core/WixToolset.Tools.Core.csproj
new file mode 100644
index 00000000..8be70e6b
--- /dev/null
+++ b/src/WixToolset.Tools.Core/WixToolset.Tools.Core.csproj
@@ -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>
diff --git a/src/WixToolset.BuildTasks/Common.cs b/src/Wixtoolset.Tools.Core/ToolsCommon.cs
index 803e9d14..37d89f3c 100644
--- a/src/WixToolset.BuildTasks/Common.cs
+++ b/src/Wixtoolset.Tools.Core/ToolsCommon.cs
@@ -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. 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 2
3namespace WixToolset 3namespace WixToolset.Tools.Core
4{ 4{
5 using System; 5 using System;
6 using System.Globalization;
7 using System.Text;
8 using System.Text.RegularExpressions; 6 using System.Text.RegularExpressions;
9 7
10 /// <summary> 8 /// <summary>
11 /// Common WixTasks utility methods and types. 9 /// Common WixTasks utility methods and types.
12 /// </summary> 10 /// </summary>
13 internal static class Common 11 public static class ToolsCommon
14 { 12 {
15 /// <summary>Metadata key name to turn off harvesting of project references.</summary> 13 /// <summary>Metadata key name to turn off harvesting of project references.</summary>
16 public const string DoNotHarvest = "DoNotHarvest"; 14 public const string DoNotHarvest = "DoNotHarvest";
@@ -24,7 +22,7 @@ namespace WixToolset
24 /// <param name="name">File/directory name to generate identifer from</param> 22 /// <param name="name">File/directory name to generate identifer from</param>
25 /// <returns>A version of the name that is a legal identifier.</returns> 23 /// <returns>A version of the name that is a legal identifier.</returns>
26 /// <remarks>This is duplicated from WiX's Common class.</remarks> 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)
28 { 26 {
29 string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_". 27 string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
30 28
diff --git a/src/test/wixcop/ConverterFixture.cs b/src/test/wixcop/ConverterFixture.cs
new file mode 100644
index 00000000..45ccc33e
--- /dev/null
+++ b/src/test/wixcop/ConverterFixture.cs
@@ -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
3namespace 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}
diff --git a/src/test/wixcop/TestData/SingleFile/ConvertedSingleFile.wxs b/src/test/wixcop/TestData/SingleFile/ConvertedSingleFile.wxs
new file mode 100644
index 00000000..aacb68fa
--- /dev/null
+++ b/src/test/wixcop/TestData/SingleFile/ConvertedSingleFile.wxs
@@ -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>
diff --git a/src/test/wixcop/TestData/SingleFile/SingleFile.wxs b/src/test/wixcop/TestData/SingleFile/SingleFile.wxs
new file mode 100644
index 00000000..310ae811
--- /dev/null
+++ b/src/test/wixcop/TestData/SingleFile/SingleFile.wxs
@@ -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>
diff --git a/src/test/wixcop/WixCopFixture.cs b/src/test/wixcop/WixCopFixture.cs
new file mode 100644
index 00000000..12863959
--- /dev/null
+++ b/src/test/wixcop/WixCopFixture.cs
@@ -0,0 +1,107 @@
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using WixBuildTools.TestSupport;
6using WixCop.CommandLine;
7using WixCop.Interfaces;
8using WixToolset.Core;
9using WixToolset.Core.TestPackage;
10using WixToolset.Extensibility;
11using WixToolset.Extensibility.Services;
12using Xunit;
13
14namespace 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}
diff --git a/src/test/wixcop/WixCopTests.csproj b/src/test/wixcop/WixCopTests.csproj
new file mode 100644
index 00000000..0ae50dc8
--- /dev/null
+++ b/src/test/wixcop/WixCopTests.csproj
@@ -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>
diff --git a/src/wix/Program.cs b/src/wix/Program.cs
index a95851ee..b0e3bb73 100644
--- a/src/wix/Program.cs
+++ b/src/wix/Program.cs
@@ -13,6 +13,7 @@ namespace WixToolset.Tools
13 using WixToolset.Extensibility; 13 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Data; 14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services; 15 using WixToolset.Extensibility.Services;
16 using WixToolset.Tools.Core;
16 17
17 /// <summary> 18 /// <summary>
18 /// Wix Toolset Command-Line Interface. 19 /// Wix Toolset Command-Line Interface.
@@ -79,52 +80,5 @@ namespace WixToolset.Tools
79 80
80 return extensionManager; 81 return extensionManager;
81 } 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 }
129 } 83 }
130} 84}
diff --git a/src/wix/wix.csproj b/src/wix/wix.csproj
index 6ec22ec0..2cbcdf3a 100644
--- a/src/wix/wix.csproj
+++ b/src/wix/wix.csproj
@@ -17,6 +17,10 @@
17 </PropertyGroup> 17 </PropertyGroup>
18 18
19 <ItemGroup> 19 <ItemGroup>
20 <ProjectReference Include="..\WixToolset.Tools.Core\WixToolset.Tools.Core.csproj" />
21 </ItemGroup>
22
23 <ItemGroup>
20 <ProjectReference Include="$(WixToolsetRootFolder)\Core\src\WixToolset.Core\WixToolset.Core.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " /> 24 <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') " /> 25 <PackageReference Include="WixToolset.Core" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core\README.md') " />
22 26
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 @@
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Xml;
6using WixToolset.Extensibility.Data;
7using WixToolset.Extensibility.Services;
8
9namespace 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 @@
1using System;
2using WixToolset.Extensibility.Data;
3
4namespace 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 @@
1using System;
2using System.Collections.Generic;
3using WixCop.Interfaces;
4using WixToolset.Core;
5using WixToolset.Extensibility.Data;
6using WixToolset.Extensibility.Services;
7
8namespace 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
3namespace 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 @@
1using WixToolset.Extensibility.Data;
2
3namespace 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
3namespace 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>