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