From 31dcabdfd71a11ac72a7ff088170c81d7b0b93b3 Mon Sep 17 00:00:00 2001 From: Igor Velikorossov Date: Wed, 21 Jul 2021 20:34:05 +1000 Subject: [PATCH 1/4] Add ApplicationConfiguration.Initialize() source generator Add a C# source generator that emits application bootstrap as described in https://github.com/dotnet/designs/blob/main/accepted/2021/winforms/streamline-application-bootstrap.md for the following properties: * ApplicationVisualStyles * ApplicationDefaultFont * ApplicationHighDpiMode * ApplicationUseCompatibleTextRendering For top level statements the bootstrap also emits code to set STA mode. A build will fail if any of the properties contain invalid or otherwise incorrect values. FontConverter is not available in netstandard 2.0, to bridge the gap FontConverter logic has been partially copied from the corresponding .NET runtime implementation to facilitate the comparable behaviour. The only missing gap is parsing of FontFamily (which requires GDI+ calls), which means that if a supplied font descriptor contains an invalid font name it will cause an app to crash. This however is a desired behaviour. --- .gitignore | 1 + Winforms.sln | 56 ++++- eng/Localize/LocProject.json | 5 + eng/Versions.props | 12 +- .../ApplicationConfigurationGenerator.Help.md | 1 + .../src/AnalyzerReleases.Shipped.md | 3 + .../src/AnalyzerReleases.Unshipped.md | 8 + .../src/DiagnosticDescriptors.cs | 50 +++++ .../src/Properties/AssemblyInfo.cs | 7 + .../src/Properties/launchSettings.json | 8 + .../src/Resources/SR.resx | 88 ++++++++ ...stem.Windows.Forms.Analyzers.CSharp.csproj | 35 +++ .../src/System.Windows.Forms.Analyzers.props | 25 +++ ...AnalyzerConfigOptionsProviderExtensions.cs | 23 ++ .../ApplicationConfig.FontDescriptor.cs | 39 ++++ .../Generators/ApplicationConfig.FontStyle.cs | 35 +++ .../ApplicationConfig.GraphicsUnit.cs | 42 ++++ .../Forms/Generators/ApplicationConfig.cs | 36 +++ .../ApplicationConfigurationGenerator.cs | 67 ++++++ ...plicationConfigurationInitializeBuilder.cs | 101 +++++++++ .../ApplicationConfigurationSyntaxReceiver.cs | 60 +++++ .../ProjectFileReader.FontConverter.cs | 176 +++++++++++++++ .../Forms/Generators/ProjectFileReader.cs | 126 +++++++++++ .../CompilerAnalyzerConfigOptions.cs | 29 +++ .../CompilerAnalyzerConfigOptionsProvider.cs | 46 ++++ ...indows.Forms.Analyzers.CSharp.Tests.csproj | 33 +++ .../ApplicationConfigTests.FontDescriptor.cs | 51 +++++ .../ApplicationConfigTests.FontStyle.cs | 34 +++ .../ApplicationConfigTests.GraphicsUnit.cs | 37 ++++ .../Generators/ApplicationConfigTests.cs | 11 + .../ApplicationConfigurationGeneratorTests.cs | 206 +++++++++++++++++ ...Initialize_DefaultFont=Tahoma.verified.txt | 34 +++ ...nitialize_DefaultFont=default.verified.txt | 34 +++ ...teInitialize_DefaultFont=null.verified.txt | 32 +++ ...lize_EnableVisualStyles=false.verified.txt | 30 +++ ...alize_EnableVisualStyles=true.verified.txt | 32 +++ ...ze_UseCompTextRendering=false.verified.txt | 32 +++ ...ize_UseCompTextRendering=true.verified.txt | 32 +++ ...tionConfigurationInitializeBuilderTests.cs | 106 +++++++++ ....GenerateInitialize_default_boilerplate.cs | 30 +++ ...ts.GenerateInitialize_default_top_level.cs | 32 +++ ...ateInitialize_user_settings_boilerplate.cs | 32 +++ ...Tests.GenerateInitialize_user_top_level.cs | 34 +++ ...tializeBuilderTests.default_boilerplate.cs | 30 +++ ...nitializeBuilderTests.default_top_level.cs | 32 +++ .../ProjectFileReaderTests.FontConverter.cs | 101 +++++++++ .../Generators/ProjectFileReaderTests.cs | 208 ++++++++++++++++++ .../tests/UnitTests/TestAdditionalText.cs | 37 ++++ .../Verifiers/CSharpSourceGeneratorTest`1.cs | 76 +++++++ .../src/System/Windows/Forms/HighDpiMode.cs | 4 + ...ndows.Forms.PrivateSourceGenerators.csproj | 3 +- ...Forms.PrivateSourceGenerators.Tests.csproj | 5 - .../WinformsControlsTest/Program.cs | 2 +- 53 files changed, 2394 insertions(+), 15 deletions(-) create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/ApplicationConfigurationGenerator.Help.md create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/AnalyzerReleases.Shipped.md create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/AnalyzerReleases.Unshipped.md create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/DiagnosticDescriptors.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/Properties/AssemblyInfo.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/Properties/launchSettings.json create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/Resources/SR.resx create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.props create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/AnalyzerConfigOptionsProviderExtensions.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontDescriptor.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontStyle.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.GraphicsUnit.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationGenerator.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationSyntaxReceiver.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.FontConverter.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/CompilerAnalyzerConfigOptions.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/CompilerAnalyzerConfigOptionsProvider.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System.Windows.Forms.Analyzers.CSharp.Tests.csproj create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontDescriptor.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontStyle.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.GraphicsUnit.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationGeneratorTests.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=Tahoma.verified.txt create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=default.verified.txt create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=null.verified.txt create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_EnableVisualStyles=false.verified.txt create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_EnableVisualStyles=true.verified.txt create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_UseCompTextRendering=false.verified.txt create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_UseCompTextRendering=true.verified.txt create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_default_boilerplate.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_default_top_level.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_user_settings_boilerplate.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_user_top_level.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationInitializeBuilderTests.default_boilerplate.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationInitializeBuilderTests.default_top_level.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.FontConverter.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/TestAdditionalText.cs create mode 100644 src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/Verifiers/CSharpSourceGeneratorTest`1.cs diff --git a/.gitignore b/.gitignore index c574ce6112e..f6e7125061f 100644 --- a/.gitignore +++ b/.gitignore @@ -374,3 +374,4 @@ Temporary Items # Ignore "InteropTests/NativeTests/out" InteropTests/NativeTests/out /src/System.Windows.Forms/src/comctl32.dll +*.received.* diff --git a/Winforms.sln b/Winforms.sln index 3c29397fe7f..15231e7af04 100644 --- a/Winforms.sln +++ b/Winforms.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.28627.84 +VisualStudioVersion = 16.0.31329.18 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinformsControlsTest", "src\System.Windows.Forms\tests\IntegrationTests\WinformsControlsTest\WinformsControlsTest.csproj", "{657472B8-FDA3-49DF-B8BE-0246046A4348}" EndProject @@ -84,7 +84,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat docs\winforms-designer.md = docs\winforms-designer.md EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "accessibility", "accessibility", "{D390F7D2-1E11-4DEE-B7F1-4FD0681A81F0}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Accessibility", "Accessibility", "{D390F7D2-1E11-4DEE-B7F1-4FD0681A81F0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccessibilityTests", "src\System.Windows.Forms\tests\AccessibilityTests\AccessibilityTests.csproj", "{A9F13504-5560-4CC7-80A2-11E23E5852E1}" EndProject @@ -163,6 +163,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiScrollBarTests", "src\S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiMDITests", "src\System.Windows.Forms\tests\IntegrationTests\MauiTests\MauiMDITests\MauiMDITests.csproj", "{5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{E4C6C5F5-46E9-4C63-9628-26752B4D9C11}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{D3AD0BF9-F5E2-4913-9AE3-9C4998F95EA1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.Analyzers.CSharp", "src\System.Windows.Forms.Analyzers.CSharp\src\System.Windows.Forms.Analyzers.CSharp.csproj", "{5025D7FF-EB6D-4250-B9C5-887ADFBBD952}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.Analyzers.CSharp.Tests", "src\System.Windows.Forms.Analyzers.CSharp\tests\UnitTests\System.Windows.Forms.Analyzers.CSharp.Tests.csproj", "{714EC82C-D4E0-4E14-9C34-4F270774D384}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -719,16 +727,16 @@ Global {83634671-CF3A-43B0-B729-42CCBA62DF2C}.Release|x64.Build.0 = Release|Any CPU {83634671-CF3A-43B0-B729-42CCBA62DF2C}.Release|x86.ActiveCfg = Release|Any CPU {83634671-CF3A-43B0-B729-42CCBA62DF2C}.Release|x86.Build.0 = Release|Any CPU - {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|Any CPU.ActiveCfg = Debug|x64 - {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|Any CPU.Build.0 = Debug|x64 + {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|Any CPU.ActiveCfg = Debug|x86 + {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|Any CPU.Build.0 = Debug|x86 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|arm64.ActiveCfg = Debug|x64 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|arm64.Build.0 = Debug|x64 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|x64.ActiveCfg = Debug|x64 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|x64.Build.0 = Debug|x64 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|x86.ActiveCfg = Debug|x86 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Debug|x86.Build.0 = Debug|x86 - {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Release|Any CPU.ActiveCfg = Release|x64 - {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Release|Any CPU.Build.0 = Release|x64 + {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Release|Any CPU.ActiveCfg = Release|x86 + {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Release|Any CPU.Build.0 = Release|x86 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Release|arm64.ActiveCfg = Release|x64 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Release|arm64.Build.0 = Release|x64 {C272DA06-B98D-4BB7-B1C4-ECF58F54B224}.Release|x64.ActiveCfg = Release|x64 @@ -943,6 +951,38 @@ Global {5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA}.Release|x64.Build.0 = Release|Any CPU {5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA}.Release|x86.ActiveCfg = Release|Any CPU {5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA}.Release|x86.Build.0 = Release|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|arm64.ActiveCfg = Debug|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|arm64.Build.0 = Debug|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|x64.ActiveCfg = Debug|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|x64.Build.0 = Debug|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|x86.ActiveCfg = Debug|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|x86.Build.0 = Debug|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Release|Any CPU.Build.0 = Release|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Release|arm64.ActiveCfg = Release|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Release|arm64.Build.0 = Release|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Release|x64.ActiveCfg = Release|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Release|x64.Build.0 = Release|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Release|x86.ActiveCfg = Release|Any CPU + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Release|x86.Build.0 = Release|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Debug|Any CPU.Build.0 = Debug|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Debug|arm64.ActiveCfg = Debug|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Debug|arm64.Build.0 = Debug|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Debug|x64.ActiveCfg = Debug|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Debug|x64.Build.0 = Debug|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Debug|x86.ActiveCfg = Debug|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Debug|x86.Build.0 = Debug|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Release|Any CPU.ActiveCfg = Release|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Release|Any CPU.Build.0 = Release|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Release|arm64.ActiveCfg = Release|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Release|arm64.Build.0 = Release|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Release|x64.ActiveCfg = Release|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Release|x64.Build.0 = Release|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Release|x86.ActiveCfg = Release|Any CPU + {714EC82C-D4E0-4E14-9C34-4F270774D384}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1004,6 +1044,10 @@ Global {05FD23CE-60AE-44A8-8DD6-1688F04BE385} = {DF68A171-D27B-4E6A-8A7E-63A651622355} {872E41E1-FF66-4B12-A273-1F26A548666F} = {8F20A905-BD37-4D80-B8DF-FA45276FC23F} {5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA} = {8F20A905-BD37-4D80-B8DF-FA45276FC23F} + {E4C6C5F5-46E9-4C63-9628-26752B4D9C11} = {77FEDB47-F7F6-490D-AF7C-ABB4A9E0B9D7} + {D3AD0BF9-F5E2-4913-9AE3-9C4998F95EA1} = {DF68A171-D27B-4E6A-8A7E-63A651622355} + {5025D7FF-EB6D-4250-B9C5-887ADFBBD952} = {E4C6C5F5-46E9-4C63-9628-26752B4D9C11} + {714EC82C-D4E0-4E14-9C34-4F270774D384} = {D3AD0BF9-F5E2-4913-9AE3-9C4998F95EA1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7B1B0433-F612-4E5A-BE7E-FCF5B9F6E136} diff --git a/eng/Localize/LocProject.json b/eng/Localize/LocProject.json index dfe4e1bdd07..31782c25fa0 100644 --- a/eng/Localize/LocProject.json +++ b/eng/Localize/LocProject.json @@ -77,6 +77,11 @@ "CopyOption": "LangIDOnName", "OutputPath": ".\\src\\System.Windows.Forms\\tests\\IntegrationTests\\WinformsControlsTest\\xlf\\" }, + { + "SourceFile": ".\\src\\System.Windows.Forms.Analyzers.CSharp\\src\\Resources\\xlf\\SR.xlf", + "CopyOption": "LangIDOnName", + "OutputPath": ".\\src\\System.Windows.Forms.Analyzers.CSharp\\src\\Resources\\xlf\\" + }, { "SourceFile": ".\\src\\System.Windows.Forms.Design\\src\\Resources\\System\\ComponentModel\\Design\\xlf\\BinaryEditor.xlf", "CopyOption": "LangIDOnName", diff --git a/eng/Versions.props b/eng/Versions.props index 8778e0ed1db..455bad30af7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -56,10 +56,17 @@ 2.4.1 $(XUnitVersion) $(XUnitVersion) - $(XUnitVersion) + 2.4.3 $(XUnitVersion) 1.0.33-beta + + + 11.18.2 + + 6.0.0-preview.4.21253.7 + 4.10.0 + 2.7.0 @@ -70,11 +77,12 @@ 3.3.2 4.0.0-1.final + 4.0.0-1.final + 1.0.1-beta1.21265.1 3.3.0-beta1.final - 4.10.0 1.0.0 3.0.0 9.0.1 diff --git a/src/System.Windows.Forms.Analyzers.CSharp/ApplicationConfigurationGenerator.Help.md b/src/System.Windows.Forms.Analyzers.CSharp/ApplicationConfigurationGenerator.Help.md new file mode 100644 index 00000000000..26ba76cdb31 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/ApplicationConfigurationGenerator.Help.md @@ -0,0 +1 @@ +# How to use ApplicationConfigurationGenerator diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/AnalyzerReleases.Shipped.md b/src/System.Windows.Forms.Analyzers.CSharp/src/AnalyzerReleases.Shipped.md new file mode 100644 index 00000000000..d567f14248e --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/AnalyzerReleases.Shipped.md @@ -0,0 +1,3 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/AnalyzerReleases.Unshipped.md b/src/System.Windows.Forms.Analyzers.CSharp/src/AnalyzerReleases.Unshipped.md new file mode 100644 index 00000000000..628d4d2fbb0 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/AnalyzerReleases.Unshipped.md @@ -0,0 +1,8 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +WFAC001 | ApplicationConfiguration | Error | ApplicationConfigurationGenerator, [Documentation](https://github.com/dotnet/winforms/blob/main/src/System.Windows.Forms.Analyzers.CSharp/ApplicationConfigurationGenerator.Help.md) +WFAC002 | ApplicationConfiguration | Error | ApplicationConfigurationGenerator, [Documentation](https://github.com/dotnet/winforms/blob/main/src/System.Windows.Forms.Analyzers.CSharp/ApplicationConfigurationGenerator.Help.md) diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/DiagnosticDescriptors.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/DiagnosticDescriptors.cs new file mode 100644 index 00000000000..62ce716d8b8 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/DiagnosticDescriptors.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; + +namespace System.Windows.Forms.Analyzers +{ + internal static class DiagnosticDescriptors + { + private const string Category = "ApplicationConfiguration"; + + private static readonly LocalizableString s_localizableWFAC001Title + = new LocalizableResourceString(nameof(SR.WFAC001Title), SR.ResourceManager, typeof(SR)); + private static readonly LocalizableString s_localizableWFAC001Message + = new LocalizableResourceString(nameof(SR.WFAC001Message), SR.ResourceManager, typeof(SR)); + private static readonly LocalizableString s_localizableWFAC002Title + = new LocalizableResourceString(nameof(SR.WFAC002Title), SR.ResourceManager, typeof(SR)); + private static readonly LocalizableString s_localizableWFAC002Message + = new LocalizableResourceString(nameof(SR.WFAC002Message), SR.ResourceManager, typeof(SR)); + private static readonly LocalizableString s_localizableWFAC002MessageWithReason + = new LocalizableResourceString(nameof(SR.WFAC002MessageWithReason), SR.ResourceManager, typeof(SR)); + + public static readonly DiagnosticDescriptor s_errorUnsupportedProjectType + = new(id: "WFAC001", + title: s_localizableWFAC001Title, + messageFormat: s_localizableWFAC001Message, + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor s_propertyCantBeSetToValue + = new(id: "WFAC002", + title: s_localizableWFAC002Title, + messageFormat: s_localizableWFAC002Message, + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor s_propertyCantBeSetToValueWithReason + = new(id: "WFAC002", + title: s_localizableWFAC002Title, +#pragma warning disable RS1032 // Define diagnostic message correctly. Justification - exception messages end with a comma. + messageFormat: s_localizableWFAC002MessageWithReason, +#pragma warning restore RS1032 // Define diagnostic message correctly + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/Properties/AssemblyInfo.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..7724dd7c685 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("System.Windows.Forms.Analyzers.CSharp.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/Properties/launchSettings.json b/src/System.Windows.Forms.Analyzers.CSharp/src/Properties/launchSettings.json new file mode 100644 index 00000000000..854617a41cb --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "System.Windows.Forms.Analyzers.CSharp": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\..\\System.Windows.Forms\\tests\\IntegrationTests\\WinformsControlsTest\\WinformsControlsTest.csproj" + } + } +} \ No newline at end of file diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/Resources/SR.resx b/src/System.Windows.Forms.Analyzers.CSharp/src/Resources/SR.resx new file mode 100644 index 00000000000..ab1ebe5e0da --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/Resources/SR.resx @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unsupported project type + + + Only projects with 'OutputType={0}' supported + + + Unsupported property value + + + ArgumentException: Project property '{0}' cannot be set to '{1}' + + + ArgumentException: Project property '{0}' cannot be set to '{1}'. Reason: {2} + + + Text "{0}" cannot be parsed. The expected text format is "{1}". + + + Value of '{0}' is not valid for font size unit. + + + Font '{0}' cannot be found. + + + Only TrueType fonts are supported. '{0}' is not a TrueType font. + + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj new file mode 100644 index 00000000000..e3998ff9f0d --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj @@ -0,0 +1,35 @@ + + + + System.Windows.Forms.Analyzers + netstandard2.0 + Preview + enable + + true + + + true + WINFORMS_ANALYZERS + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + true + System + + + + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.props b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.props new file mode 100644 index 00000000000..b9625dd51c4 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.props @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/AnalyzerConfigOptionsProviderExtensions.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/AnalyzerConfigOptionsProviderExtensions.cs new file mode 100644 index 00000000000..84abc85c1f1 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/AnalyzerConfigOptionsProviderExtensions.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; + +namespace System.Windows.Forms.Generators +{ + internal static class AnalyzerConfigOptionsProviderExtensions + { + /// + /// Attempts to read a value for the requested MSBuild property. + /// + /// The global optins. + /// The name of the property to read the value for. + /// The property's value. + /// if the property is present; otherwise . + public static bool GetMSBuildProperty(this AnalyzerConfigOptionsProvider analyzerConfigOptions, string name, out string? value) + { + return analyzerConfigOptions.GlobalOptions.TryGetValue($"build_property.{name}", out value); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontDescriptor.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontDescriptor.cs new file mode 100644 index 00000000000..60e354f38af --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontDescriptor.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.RegularExpressions; + +namespace System.Windows.Forms.Generators +{ + internal partial class ApplicationConfig + { + public class FontDescriptor + { + public FontDescriptor(string fontName, float emSize, FontStyle style, GraphicsUnit unit) + { + Name = fontName; + Size = emSize; + Style = style; + Unit = unit; + } + + public string Name { get; set; } + public float Size { get; } + public FontStyle Style { get; } + public GraphicsUnit Unit { get; } + + public override string ToString() + { + // Sanitize the name - remove anything but alpha-numeric and spaces. + string name = Regex.Replace(Name, @"[^\w\d ]", string.Empty); + + string fontFamily = string.IsNullOrWhiteSpace(name) + ? "Control.DefaultFont.FontFamily" + : $"new FontFamily(\"{name}\")"; + + return $"new Font({fontFamily}, {Size}f, (FontStyle){(int)Style}, (GraphicsUnit){(int)Unit})"; + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontStyle.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontStyle.cs new file mode 100644 index 00000000000..61ee92a7573 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontStyle.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Generators +{ + internal partial class ApplicationConfig + { + // Copied from https://github.com/dotnet/runtime/blob/00ee1c18715723e62484c9bc8a14f517455fc3b3/src/libraries/System.Drawing.Common/src/System/Drawing/FontStyle.cs + [Flags] + public enum FontStyle + { + /// + /// Normal text. + /// + Regular = 0, + /// + /// Bold text. + /// + Bold = 1, + /// + /// Italic text. + /// + Italic = 2, + /// + /// Underlined text. + /// + Underline = 4, + /// + /// Text with a line through the middle. + /// + Strikeout = 8, + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.GraphicsUnit.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.GraphicsUnit.cs new file mode 100644 index 00000000000..254fc4fd552 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.GraphicsUnit.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Generators +{ + internal partial class ApplicationConfig + { + // Copied from https://github.com/dotnet/runtime/blob/00ee1c18715723e62484c9bc8a14f517455fc3b3/src/libraries/System.Drawing.Common/src/System/Drawing/GraphicsUnit.cs + public enum GraphicsUnit + { + /// + /// Specifies the world unit as the unit of measure. + /// + World = 0, + /// + /// Specifies 1/75 inch as the unit of measure. + /// + Display = 1, + /// + /// Specifies a device pixel as the unit of measure. + /// + Pixel = 2, + /// + /// Specifies a printer's point (1/72 inch) as the unit of measure. + /// + Point = 3, + /// + /// Specifies the inch as the unit of measure. + /// + Inch = 4, + /// + /// Specifies the document unit (1/300 inch) as the unit of measure. + /// + Document = 5, + /// + /// Specifies the millimeter as the unit of measure. + /// + Millimeter = 6 + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.cs new file mode 100644 index 00000000000..57b4e12e9c2 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Generators +{ + internal partial class ApplicationConfig + { + public static class PropertyNameCSharp + { + public const string EnableVisualStyles = "ApplicationVisualStyles"; + public const string DefaultFont = "ApplicationDefaultFont"; + public const string HighDpiMode = "ApplicationHighDpiMode"; + public const string UseCompatibleTextRendering = "ApplicationUseCompatibleTextRendering"; + } + + public static class PropertyNameVisualBasic + { + public const string EnableVisualStyles = "EnableVisualStyles"; + public const string HighDpiMode = "HighDpiMode"; + } + + public static class PropertyDefaultValue + { + public const bool EnableVisualStyles = true; + public const float FontSize = 9f; + public const HighDpiMode DpiMode = HighDpiMode.PerMonitorV2; + public const bool UseCompatibleTextRendering = false; + } + + public bool EnableVisualStyles { get; set; } + public FontDescriptor? DefaultFont { get; set; } + public HighDpiMode HighDpiMode { get; set; } + public bool UseCompatibleTextRendering { get; set; } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationGenerator.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationGenerator.cs new file mode 100644 index 00000000000..387437cb0b2 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationGenerator.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Windows.Forms.Analyzers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.Windows.Forms.Generators +{ + [Generator] + internal class ApplicationConfigurationGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + if (context.SyntaxReceiver is not ApplicationConfigurationSyntaxReceiver syntaxReceiver) + { + throw new InvalidOperationException("We were given the wrong syntax receiver."); + } + + if (syntaxReceiver.Nodes.Count == 0) + { + return; + } + + if (context.Compilation.Options.OutputKind != OutputKind.WindowsApplication && + // Starting in the 5.0.100 version of the .NET SDK, when OutputType is set to Exe, it is automatically changed to WinExe + // for WPF and Windows Forms apps that target any framework version, including .NET Framework. + // https://docs.microsoft.com/en-us/dotnet/core/compatibility/sdk/5.0/automatically-infer-winexe-output-type + context.Compilation.Options.OutputKind != OutputKind.ConsoleApplication) + { + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.s_errorUnsupportedProjectType, Location.None, nameof(OutputKind.WindowsApplication))); + return; + } + + ApplicationConfig? projectConfig = ProjectFileReader.ReadApplicationConfig(context); + if (projectConfig is null) + { + return; + } + + string? code = ApplicationConfigurationInitializeBuilder.GenerateInitialize(projectNamespace: GetUserProjectNamespace(syntaxReceiver.Nodes[0]), projectConfig); + if (code is not null) + { + context.AddSource("ApplicationConfiguration.g.cs", code); + } + } + + private string? GetUserProjectNamespace(SyntaxNode node) + { + string? ns = null; + + if (node.Ancestors().FirstOrDefault(a => a is NamespaceDeclarationSyntax) is NamespaceDeclarationSyntax namespaceSyntax) + { + ns = namespaceSyntax.Name.ToString(); + } + + return ns; + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new ApplicationConfigurationSyntaxReceiver()); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs new file mode 100644 index 00000000000..e143c254cb0 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; + +namespace System.Windows.Forms.Generators +{ + internal static class ApplicationConfigurationInitializeBuilder + { + public static string GenerateInitialize(string? projectNamespace, ApplicationConfig projectConfig) + { + bool topLevelApp = string.IsNullOrWhiteSpace(projectNamespace); + string? defaultFont = projectConfig.DefaultFont?.ToString(); + string indent = topLevelApp ? string.Empty : " "; + return string.Format(topLevelApp ? TopLevelStatements : BoilerPlate, + topLevelApp ? string.Empty : projectNamespace, + GenerateCode(projectConfig, defaultFont, $"{indent} /// "), + GenerateCode(projectConfig, defaultFont, $"{indent} ")); + + static string GenerateCode(ApplicationConfig projectConfig, string? defaultFont, string indent) + { + StringBuilder code = new(); + if (projectConfig.EnableVisualStyles) + { + code.AppendLine($"{indent}Application.EnableVisualStyles();"); + } + + code.AppendLine($"{indent}Application.SetCompatibleTextRenderingDefault({projectConfig.UseCompatibleTextRendering.ToString().ToLowerInvariant()});"); + + if (!string.IsNullOrWhiteSpace(defaultFont)) + { + code.AppendLine($"{indent}Application.SetDefaultFont({defaultFont});"); + } + + // Don't append line as we don't need the trailing \r\n! + code.Append($"{indent}Application.SetHighDpiMode(HighDpiMode.{projectConfig.HighDpiMode});"); + + return code.ToString(); + } + } + + private const string BoilerPlate = @"// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Windows.Forms; + +namespace {0} +{{ + /// + /// Bootstrap the application configuration. + /// + [CompilerGenerated] + internal static partial class ApplicationConfiguration + {{ + /// + /// Bootstrap the application as follows: + /// +{1} + /// + /// + public static void Initialize() + {{ +{2} + }} + }} +}} +"; + + private const string TopLevelStatements = @"// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; +{0} +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{{ + /// + /// Bootstrap the application as follows: + /// +{1} + /// + /// + public static void Initialize() + {{ + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + +{2} + }} +}} +"; + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationSyntaxReceiver.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationSyntaxReceiver.cs new file mode 100644 index 00000000000..6e9b087e179 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationSyntaxReceiver.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace System.Windows.Forms.Generators +{ + internal class ApplicationConfigurationSyntaxReceiver : ISyntaxReceiver + { + public List Nodes { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { +#pragma warning disable SA1513 // Closing brace should be followed by blank line + if (syntaxNode is InvocationExpressionSyntax + { + ArgumentList: + { + Arguments: { Count: < 1 } + }, + Expression: MemberAccessExpressionSyntax + { + Name: + { + Identifier: + { + ValueText: "Initialize" + } + }, + Expression: + MemberAccessExpressionSyntax // For: SourceGenerated.ApplicationConfiguration.Initialize() + { + Name: + { + Identifier: + { + ValueText: "ApplicationConfiguration" + } + } + } + or + IdentifierNameSyntax // For: ApplicationConfiguration.Initialize() with a using statement + { + Identifier: + { + ValueText: "ApplicationConfiguration" + } + } + } + }) + { + Nodes.Add(syntaxNode); + } +#pragma warning restore SA1513 // Closing brace should be followed by blank line + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.FontConverter.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.FontConverter.cs new file mode 100644 index 00000000000..ec614b062ce --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.FontConverter.cs @@ -0,0 +1,176 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Globalization; +using static System.Windows.Forms.Generators.ApplicationConfig; + +namespace System.Windows.Forms.Generators +{ + internal static partial class ProjectFileReader + { + // Copied from https://github.com/dotnet/runtime/blob/00ee1c18715723e62484c9bc8a14f517455fc3b3/src/libraries/System.Drawing.Common/src/System/Drawing/FontConverter.cs + internal static class FontConverter + { + private const string StylePrefix = "style="; + private static readonly CultureInfo s_culture = CultureInfo.InvariantCulture; + + public static FontDescriptor? ConvertFrom(string font) + { + font = font!.Trim(); + + // Expected string format: "name[, size[, units[, style=style1[, style2[...]]]]]" + // Example using 'vi-VN' culture: "Microsoft Sans Serif, 8,25pt, style=Italic, Bold" + if (font.Length == 0) + { + return null; + } + + char separator = s_culture.TextInfo.ListSeparator[0]; // For vi-VN: ',' + string fontName = font; // start with the assumption that only the font name was provided. + string? style = null; + string? sizeStr; + float fontSize = PropertyDefaultValue.FontSize; + FontStyle fontStyle = FontStyle.Regular; + GraphicsUnit units = GraphicsUnit.Point; + + // Get the index of the first separator (would indicate the end of the name in the string). + int nameIndex = font.IndexOf(separator); + + if (nameIndex < 0) + { + return new FontDescriptor(fontName, fontSize, fontStyle, units); + } + + // Some parameters are provided in addition to name. + fontName = font.Substring(0, nameIndex); + + if (nameIndex < font.Length - 1) + { + // Get the style index (if any). The size is a bit problematic because it can be formatted differently + // depending on the culture, we'll parse it last. + int styleIndex = CultureInfo.InvariantCulture.CompareInfo.IndexOf(font, StylePrefix, CompareOptions.IgnoreCase); + + if (styleIndex != -1) + { + // style found. + style = font.Substring(styleIndex, font.Length - styleIndex); + + // Get the mid-substring containing the size information. + sizeStr = font.Substring(nameIndex + 1, styleIndex - nameIndex - 1); + } + else + { + // no style. + sizeStr = font.Substring(nameIndex + 1); + } + + // Parse size. + (string? size, string? unit) unitTokens = ParseSizeTokens(sizeStr, separator); + + if (unitTokens.size != null) + { + try + { + fontSize = (float)TypeDescriptor.GetConverter(typeof(float)).ConvertFromString(null, s_culture, unitTokens.size); + } + catch + { + // Exception from converter is too generic. + throw new ArgumentException(string.Format(SR.TextParseFailedFormat, + font, + $"name{separator} size[units[{separator} style=style1[{separator} style2{separator} ...]]]")); + } + } + + if (unitTokens.unit != null) + { + // ParseGraphicsUnits throws an ArgumentException if format is invalid. + units = ParseGraphicsUnits(unitTokens.unit); + } + + if (style != null) + { + // Parse FontStyle + style = style.Substring(6); // style string always starts with style= + string[] styleTokens = style.Split(separator); + + for (int tokenCount = 0; tokenCount < styleTokens.Length; tokenCount++) + { + string styleText = styleTokens[tokenCount]; + styleText = styleText.Trim(); + + fontStyle |= (FontStyle)Enum.Parse(typeof(FontStyle), styleText, true); + + // Enum.IsDefined doesn't do what we want on flags enums... + FontStyle validBits = FontStyle.Regular | FontStyle.Bold | FontStyle.Italic | FontStyle.Underline | FontStyle.Strikeout; + if ((fontStyle | validBits) != validBits) + { + throw new InvalidEnumArgumentException(nameof(style), (int)fontStyle, typeof(FontStyle)); + } + } + } + } + + return new FontDescriptor(fontName, fontSize, fontStyle, units); + } + + private static GraphicsUnit ParseGraphicsUnits(string units) => + units switch + { + // Display unit is not supported + // https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/System.Drawing.Common/src/System/Drawing/FontConverter.cs#L446-L463 + // "display" => GraphicsUnit.Display, + "doc" => GraphicsUnit.Document, + "pt" => GraphicsUnit.Point, + "in" => GraphicsUnit.Inch, + "mm" => GraphicsUnit.Millimeter, + "px" => GraphicsUnit.Pixel, + "world" => GraphicsUnit.World, + _ => throw new ArgumentException(string.Format(SR.InvalidArgumentValueFontConverter, units), nameof(units)), + }; + + private static (string?, string?) ParseSizeTokens(string text, char separator) + { + string? size = null; + string? units = null; + + text = text.Trim(); + + int length = text.Length; + int splitPoint; + + if (length > 0) + { + // text is expected to have a format like " 8,25pt, ". Leading and trailing spaces (trimmed above), + // last comma, unit and decimal value may not appear. We need to make it ####.##CC + for (splitPoint = 0; splitPoint < length; splitPoint++) + { + if (char.IsLetter(text[splitPoint])) + { + break; + } + } + + char[] trimChars = new char[] { separator, ' ' }; + + if (splitPoint > 0) + { + size = text.Substring(0, splitPoint); + // Trimming spaces between size and units. + size = size.Trim(trimChars); + } + + if (splitPoint < length) + { + units = text.Substring(splitPoint); + units = units.TrimEnd(trimChars); + } + } + + return (size, units); + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.cs new file mode 100644 index 00000000000..6ed0f6a178e --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows.Forms.Analyzers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static System.Windows.Forms.Generators.ApplicationConfig; + +namespace System.Windows.Forms.Generators +{ + internal static partial class ProjectFileReader + { + public static ApplicationConfig? ReadApplicationConfig(GeneratorExecutionContext context) + { + Diagnostic? diagnostic; + if (!TryReadBool(context.AnalyzerConfigOptions, PropertyNameCSharp.EnableVisualStyles, + defaultValue: PropertyDefaultValue.EnableVisualStyles, + out bool enableVisualStyles, out diagnostic) || + !TryReadBool(context.AnalyzerConfigOptions, PropertyNameCSharp.UseCompatibleTextRendering, + defaultValue: PropertyDefaultValue.UseCompatibleTextRendering, + out bool useCompatibleTextRendering, out diagnostic) || + !TryReadFont(context.AnalyzerConfigOptions, out FontDescriptor? font, out diagnostic) || + !TryReadHighDpiMode(context.AnalyzerConfigOptions, out HighDpiMode highDpiMode, out diagnostic)) + { + context.ReportDiagnostic(diagnostic!); + return null; + } + + ApplicationConfig projectConfig = new() + { + EnableVisualStyles = enableVisualStyles, + DefaultFont = font, + HighDpiMode = highDpiMode, + UseCompatibleTextRendering = useCompatibleTextRendering + }; + + return projectConfig; + } + + private static bool TryReadBool(AnalyzerConfigOptionsProvider configOptions, string propertyName, bool defaultValue, out bool value, out Diagnostic? diagnostic) + { + value = defaultValue; + diagnostic = null; + + if (!configOptions.GetMSBuildProperty(propertyName, out string? rawValue) || + rawValue == string.Empty) + { + // The property is either not defined explicitly, or the value is "". All good, use the default. + return true; + } + + if (!bool.TryParse(rawValue, out value)) + { + diagnostic = Diagnostic.Create(DiagnosticDescriptors.s_propertyCantBeSetToValue, + Location.None, + propertyName, + rawValue); + value = defaultValue; + return false; + } + + return true; + } + + private static bool TryReadFont(AnalyzerConfigOptionsProvider configOptions, out FontDescriptor? font, out Diagnostic? diagnostic) + { + font = null; + diagnostic = null; + + if (!configOptions.GetMSBuildProperty(PropertyNameCSharp.DefaultFont, out string? rawValue) || + rawValue == string.Empty) + { + // The property is either not defined explicitly, or the value is "". All good, use the default. + return true; + } + + try + { + // In .NET runtime the font is validated via GDI+ to see whether it can be mapped to a valid font family. + // We don't have access to Font (though we can with some gymnastics) or FontConverter (at all), so our + // font validation logic is not as exhaustive as it is in .NET runtime. + // With that it is possible that the value is not a valid font (e.g. 'Style=Bold' or '11px'), and which + // will lead to runtime failures when we execute SetDefaultFont(new FontFamily('Style=Bold')). + font = FontConverter.ConvertFrom(rawValue!); + return true; + } + catch (Exception ex) + { + diagnostic = Diagnostic.Create(DiagnosticDescriptors.s_propertyCantBeSetToValueWithReason, + Location.None, + PropertyNameCSharp.DefaultFont, + rawValue, + ex.Message); + } + + return false; + } + + private static bool TryReadHighDpiMode(AnalyzerConfigOptionsProvider configOptions, out HighDpiMode highDpiMode, out Diagnostic? diagnostic) + { + highDpiMode = PropertyDefaultValue.DpiMode; + diagnostic = null; + + if (!configOptions.GetMSBuildProperty(PropertyNameCSharp.HighDpiMode, out string? rawValue) || + rawValue == string.Empty) + { + // The property is either not defined explicitly, or the value is "". All good, use the default. + return true; + } + + if (!Enum.TryParse(rawValue, true, out highDpiMode) || + !Enum.IsDefined(typeof(HighDpiMode), highDpiMode)) + { + diagnostic = Diagnostic.Create(DiagnosticDescriptors.s_propertyCantBeSetToValue, + Location.None, + PropertyNameCSharp.HighDpiMode, + rawValue); + highDpiMode = PropertyDefaultValue.DpiMode; + return false; + } + + return true; + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/CompilerAnalyzerConfigOptions.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/CompilerAnalyzerConfigOptions.cs new file mode 100644 index 00000000000..a0bbb25b0b7 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/CompilerAnalyzerConfigOptions.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace System.Windows.Forms.Analyzers.Tests +{ + // Borrowed from https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs + + [ExcludeFromCodeCoverage] + internal sealed class CompilerAnalyzerConfigOptions : AnalyzerConfigOptions + { + public static CompilerAnalyzerConfigOptions Empty { get; } = new CompilerAnalyzerConfigOptions(ImmutableDictionary.Create()); + + private readonly ImmutableDictionary _backing; + + public CompilerAnalyzerConfigOptions(ImmutableDictionary properties) + { + _backing = properties; + } + + public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) => _backing.TryGetValue(key, out value); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/CompilerAnalyzerConfigOptionsProvider.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/CompilerAnalyzerConfigOptionsProvider.cs new file mode 100644 index 00000000000..3d28cf5af7f --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/CompilerAnalyzerConfigOptionsProvider.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace System.Windows.Forms.Analyzers.Tests +{ + // Borrowed from https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilerAnalyzerConfigOptionsProvider.cs + + [ExcludeFromCodeCoverage] + internal sealed class CompilerAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider + { + private readonly ImmutableDictionary _treeDict; + + public static CompilerAnalyzerConfigOptionsProvider Empty { get; } + = new CompilerAnalyzerConfigOptionsProvider( + ImmutableDictionary.Empty, + CompilerAnalyzerConfigOptions.Empty); + + internal CompilerAnalyzerConfigOptionsProvider( + ImmutableDictionary treeDict, + AnalyzerConfigOptions globalOptions) + { + _treeDict = treeDict; + GlobalOptions = globalOptions; + } + + public override AnalyzerConfigOptions GlobalOptions { get; } + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) + => _treeDict.TryGetValue(tree, out var options) ? options : CompilerAnalyzerConfigOptions.Empty; + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + => _treeDict.TryGetValue(textFile, out var options) ? options : CompilerAnalyzerConfigOptions.Empty; + + internal CompilerAnalyzerConfigOptionsProvider WithAdditionalTreeOptions(ImmutableDictionary treeDict) + => new(_treeDict.AddRange(treeDict), GlobalOptions); + + internal CompilerAnalyzerConfigOptionsProvider WithGlobalOptions(AnalyzerConfigOptions globalOptions) + => new(_treeDict, globalOptions); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System.Windows.Forms.Analyzers.CSharp.Tests.csproj b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System.Windows.Forms.Analyzers.CSharp.Tests.csproj new file mode 100644 index 00000000000..e5f38e3f97a --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System.Windows.Forms.Analyzers.CSharp.Tests.csproj @@ -0,0 +1,33 @@ + + + + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontDescriptor.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontDescriptor.cs new file mode 100644 index 00000000000..228aa471270 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontDescriptor.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using static System.Windows.Forms.Generators.ApplicationConfig; +using Xunit; +using Xunit.Abstractions; + +namespace System.Windows.Forms.Analyzers.Generators.Tests +{ + public partial class ApplicationConfigTests + { + public class FontDescriptorTests + { + private readonly ITestOutputHelper _output; + + public FontDescriptorTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void FontDescriptor_ctor() + { + FontDescriptor descriptor = new("fontName", 10f, FontStyle.Bold | FontStyle.Italic, GraphicsUnit.Point); + + Assert.Equal("fontName", descriptor.Name); + Assert.Equal(10f, descriptor.Size); + Assert.Equal(FontStyle.Bold | FontStyle.Italic, descriptor.Style); + Assert.Equal(GraphicsUnit.Point, descriptor.Unit); + } + + [Theory] + [InlineData("", "new Font(Control.DefaultFont.FontFamily, 10f, (FontStyle)3, (GraphicsUnit)3)")] + [InlineData(" ", "new Font(Control.DefaultFont.FontFamily, 10f, (FontStyle)3, (GraphicsUnit)3)")] + [InlineData("\t", "new Font(Control.DefaultFont.FontFamily, 10f, (FontStyle)3, (GraphicsUnit)3)")] + [InlineData("fontName", "new Font(new FontFamily(\"fontName\"), 10f, (FontStyle)3, (GraphicsUnit)3)")] + [InlineData("\"fontName\"", "new Font(new FontFamily(\"fontName\"), 10f, (FontStyle)3, (GraphicsUnit)3)")] + [InlineData("Name with \tspaces", "new Font(new FontFamily(\"Name with spaces\"), 10f, (FontStyle)3, (GraphicsUnit)3)")] + [InlineData("Name with 'quotes'", "new Font(new FontFamily(\"Name with quotes\"), 10f, (FontStyle)3, (GraphicsUnit)3)")] + [InlineData("Name with \r\n lines", "new Font(new FontFamily(\"Name with lines\"), 10f, (FontStyle)3, (GraphicsUnit)3)")] + public void FontDescriptor_ToString(string fontName, string expected) + { + FontDescriptor descriptor = new(fontName, 10f, FontStyle.Bold | FontStyle.Italic, GraphicsUnit.Point); + + _output.WriteLine(descriptor.ToString()); + Assert.Equal(expected, descriptor.ToString()); + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontStyle.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontStyle.cs new file mode 100644 index 00000000000..f94cf54d8a3 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontStyle.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; +using static System.Windows.Forms.Generators.ApplicationConfig; + +namespace System.Windows.Forms.Analyzers.Generators.Tests +{ + public partial class ApplicationConfigTests + { + public class FontStyleTests + { + [Fact] + public void GetStandardValuesTest() + { + var values = Enum.GetValues(typeof(FontStyle)); + Assert.Equal(5, values.Length); // The values of Graphics unit: Regular, Bold, Italic, Underline, Strikeout. + } + + [Theory] + [InlineData("Bold", FontStyle.Bold)] + [InlineData("Italic", FontStyle.Italic)] + [InlineData("Regular", FontStyle.Regular)] + [InlineData("Strikeout", FontStyle.Strikeout)] + [InlineData("Underline", FontStyle.Underline)] + internal void CanConvertFrom(string input, FontStyle expected) + { + FontStyle value = Enum.Parse(input); + Assert.Equal(expected, value); + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.GraphicsUnit.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.GraphicsUnit.cs new file mode 100644 index 00000000000..2cdffab4e49 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.GraphicsUnit.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; +using static System.Windows.Forms.Generators.ApplicationConfig; + +namespace System.Windows.Forms.Analyzers.Generators.Tests +{ + public partial class ApplicationConfigTests + { + // Copied from https://github.com/dotnet/runtime/blob/00ee1c18715723e62484c9bc8a14f517455fc3b3/src/libraries/System.Drawing.Common/tests/System/Drawing/FontConverterTests.cs#L203 + public class GraphicsUnitTests + { + [Fact] + public void GetStandardValuesTest() + { + var values = Enum.GetValues(typeof(GraphicsUnit)); + Assert.Equal(7, values.Length); // The values of Graphics unit: World, Display, Pixel, Point, Inch, Document, Millimeter. + } + + [Theory] + [InlineData("Display", GraphicsUnit.Display)] + [InlineData("Document", GraphicsUnit.Document)] + [InlineData("Inch", GraphicsUnit.Inch)] + [InlineData("Millimeter", GraphicsUnit.Millimeter)] + [InlineData("Pixel", GraphicsUnit.Pixel)] + [InlineData("Point", GraphicsUnit.Point)] + [InlineData("World", GraphicsUnit.World)] + internal void CanConvertFrom(string input, GraphicsUnit expected) + { + GraphicsUnit value = Enum.Parse(input); + Assert.Equal(expected, value); + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.cs new file mode 100644 index 00000000000..d6785c77d47 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Forms.Analyzers.Generators.Tests +{ + public partial class ApplicationConfigTests + { + // See nested files + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationGeneratorTests.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationGeneratorTests.cs new file mode 100644 index 00000000000..8193b9ff526 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationGeneratorTests.cs @@ -0,0 +1,206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using System.Windows.Forms.Analyzers.Tests; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using Xunit; +using static System.Windows.Forms.Generators.ApplicationConfig; + +namespace System.Windows.Forms.Generators.Tests +{ + public partial class ApplicationConfigurationGeneratorTests + { + private const string SourceCompilable = @" +namespace MyProject +{ + class Program + { + static void Main() + { + ApplicationConfiguration.Initialize(); + } + } +}"; + private const string SourceCompilationFailed = @" +namespace MyProject +{ + class Program + { + static void Main() + { + {|CS0103:ApplicationConfiguration|}.Initialize(); + } + } +}"; + + public static IEnumerable UnsupportedProjectTypes_TestData() + { + foreach (OutputKind projectType in Enum.GetValues(typeof(OutputKind))) + { + if (projectType != OutputKind.ConsoleApplication && + projectType != OutputKind.WindowsApplication) + { + yield return new object[] { projectType }; + } + } + } + + [Theory] + [MemberData(nameof(UnsupportedProjectTypes_TestData))] + public async Task ApplicationConfigurationGenerator_GenerateInitialize_fails_if_project_type_unsupported(OutputKind projectType) + { + var test = new CSharpSourceGeneratorTest + { + TestState = + { + OutputKind = projectType, + Sources = { SourceCompilationFailed }, + ExpectedDiagnostics = + { + DiagnosticResult.CompilerError("WFAC001").WithArguments("WindowsApplication"), + } + }, + }; + + await test.RunAsync().ConfigureAwait(false); + } + + [Theory] + [InlineData(OutputKind.ConsoleApplication)] + [InlineData(OutputKind.WindowsApplication)] + public async Task ApplicationConfigurationGenerator_GenerateInitialize_pass_if_supported_project_type(OutputKind projectType) + { + SourceText generatedCode = LoadFileContent("GenerateInitialize_default_boilerplate"); + + var test = new CSharpSourceGeneratorTest + { + TestState = + { + OutputKind = projectType, + Sources = { SourceCompilable }, + GeneratedSources = + { + (typeof(ApplicationConfigurationGenerator), "ApplicationConfiguration.g.cs", generatedCode), + }, + }, + }; + + await test.RunAsync().ConfigureAwait(false); + } + + [Fact] + public async Task ApplicationConfigurationGenerator_GenerateInitialize_default_boilerplate() + { + SourceText generatedCode = LoadFileContent("GenerateInitialize_default_boilerplate"); + + var test = new CSharpSourceGeneratorTest + { + TestState = + { + OutputKind = OutputKind.WindowsApplication, + Sources = { SourceCompilable }, + GeneratedSources = + { + (typeof(ApplicationConfigurationGenerator), "ApplicationConfiguration.g.cs", generatedCode), + }, + }, + }; + + await test.RunAsync().ConfigureAwait(false); + } + + [Fact] + public async Task ApplicationConfigurationGenerator_GenerateInitialize_user_settings_boilerplate() + { + SourceText generatedCode = LoadFileContent("GenerateInitialize_user_settings_boilerplate"); + + var test = new CSharpSourceGeneratorTest + { + GlobalOptions = + { + ($"build_property.{PropertyNameCSharp.DefaultFont}", "Microsoft Sans Serif, 8.25px"), + ($"build_property.{PropertyNameCSharp.EnableVisualStyles}", ""), + ($"build_property.{PropertyNameCSharp.HighDpiMode}", HighDpiMode.SystemAware.ToString()), + ($"build_property.{PropertyNameCSharp.UseCompatibleTextRendering}", "true"), + }, + TestState = + { + OutputKind = OutputKind.WindowsApplication, + Sources = { SourceCompilable }, + GeneratedSources = + { + (typeof(ApplicationConfigurationGenerator), "ApplicationConfiguration.g.cs", generatedCode), + }, + }, + }; + + await test.RunAsync().ConfigureAwait(false); + } + + [Fact] + public async Task ApplicationConfigurationGenerator_GenerateInitialize_default_top_level() + { + const string source = @" +ApplicationConfiguration.Initialize(); +"; + + SourceText generatedCode = LoadFileContent("GenerateInitialize_default_top_level"); + + var test = new CSharpSourceGeneratorTest + { + TestState = + { + OutputKind = OutputKind.WindowsApplication, + Sources = { source }, + GeneratedSources = + { + (typeof(ApplicationConfigurationGenerator), "ApplicationConfiguration.g.cs", generatedCode), + }, + }, + }; + + await test.RunAsync().ConfigureAwait(false); + } + + [Fact] + public async Task ApplicationConfigurationGenerator_GenerateInitialize_user_settings_top_level() + { + const string source = @" +ApplicationConfiguration.Initialize(); +"; + + SourceText generatedCode = LoadFileContent("GenerateInitialize_user_top_level"); + + var test = new CSharpSourceGeneratorTest + { + GlobalOptions = + { + ($"build_property.{PropertyNameCSharp.DefaultFont}", "Microsoft Sans Serif, 8.25px"), + ($"build_property.{PropertyNameCSharp.EnableVisualStyles}", ""), + ($"build_property.{PropertyNameCSharp.HighDpiMode}", HighDpiMode.SystemAware.ToString()), + ($"build_property.{PropertyNameCSharp.UseCompatibleTextRendering}", "true"), + }, + TestState = + { + OutputKind = OutputKind.WindowsApplication, + Sources = { source }, + GeneratedSources = + { + (typeof(ApplicationConfigurationGenerator), "ApplicationConfiguration.g.cs", generatedCode), + }, + }, + }; + + await test.RunAsync().ConfigureAwait(false); + } + + private SourceText LoadFileContent(string testName) + => SourceText.From( + File.ReadAllText($@"System\Windows\Forms\Generators\MockData\{GetType().Name}.{testName}.cs"), + Encoding.UTF8); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=Tahoma.verified.txt b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=Tahoma.verified.txt new file mode 100644 index 00000000000..73352e47ad1 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=Tahoma.verified.txt @@ -0,0 +1,34 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(true); + /// Application.SetDefaultFont(new Font(new FontFamily("Tahoma"), 12f, (FontStyle)0, (GraphicsUnit)3)); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(true); + Application.SetDefaultFont(new Font(new FontFamily("Tahoma"), 12f, (FontStyle)0, (GraphicsUnit)3)); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=default.verified.txt b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=default.verified.txt new file mode 100644 index 00000000000..336c79ed031 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=default.verified.txt @@ -0,0 +1,34 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(true); + /// Application.SetDefaultFont(new Font(Control.DefaultFont.FontFamily, 12f, (FontStyle)3, (GraphicsUnit)6)); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(true); + Application.SetDefaultFont(new Font(Control.DefaultFont.FontFamily, 12f, (FontStyle)3, (GraphicsUnit)6)); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=null.verified.txt b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=null.verified.txt new file mode 100644 index 00000000000..fe67c265d4d --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_DefaultFont=null.verified.txt @@ -0,0 +1,32 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(false); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_EnableVisualStyles=false.verified.txt b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_EnableVisualStyles=false.verified.txt new file mode 100644 index 00000000000..2b72d99d38e --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_EnableVisualStyles=false.verified.txt @@ -0,0 +1,30 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.SetCompatibleTextRenderingDefault(false); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.SetCompatibleTextRenderingDefault(false); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_EnableVisualStyles=true.verified.txt b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_EnableVisualStyles=true.verified.txt new file mode 100644 index 00000000000..fe67c265d4d --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_EnableVisualStyles=true.verified.txt @@ -0,0 +1,32 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(false); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_UseCompTextRendering=false.verified.txt b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_UseCompTextRendering=false.verified.txt new file mode 100644 index 00000000000..fe67c265d4d --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_UseCompTextRendering=false.verified.txt @@ -0,0 +1,32 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(false); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_UseCompTextRendering=true.verified.txt b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_UseCompTextRendering=true.verified.txt new file mode 100644 index 00000000000..5fcfb37097c --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.GenerateInitialize_UseCompTextRendering=true.verified.txt @@ -0,0 +1,32 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(true); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(true); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.cs new file mode 100644 index 00000000000..90f0d841ba6 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using VerifyXunit; +using Xunit; +using static System.Windows.Forms.Generators.ApplicationConfig; + +namespace System.Windows.Forms.Generators.Tests +{ + [UsesVerify] + public partial class ApplicationConfigurationInitializeBuilderTests + { + [Theory] + [InlineData(null, "default_top_level")] + [InlineData("", "default_top_level")] + [InlineData(" ", "default_top_level")] + [InlineData("\t", "default_top_level")] + [InlineData("MyProject", "default_boilerplate")] + public void ApplicationConfigurationInitializeBuilder_GenerateInitialize_can_handle_namespace(string ns, string expectedFileName) + { + string expected = File.ReadAllText($@"System\Windows\Forms\Generators\MockData\{GetType().Name}.{expectedFileName}.cs"); + + string output = ApplicationConfigurationInitializeBuilder.GenerateInitialize(ns, + new ApplicationConfig + { + DefaultFont = null, + EnableVisualStyles = PropertyDefaultValue.EnableVisualStyles, + HighDpiMode = PropertyDefaultValue.DpiMode, + UseCompatibleTextRendering = PropertyDefaultValue.UseCompatibleTextRendering + }); + + Assert.Equal(expected, output); + } + + internal static TheoryData<(object, string)> GenerateInitializeData() + => new() + { + // EnableVisualStyles: false, true + (new ApplicationConfig + { + DefaultFont = null, + EnableVisualStyles = false, + HighDpiMode = PropertyDefaultValue.DpiMode, + UseCompatibleTextRendering = PropertyDefaultValue.UseCompatibleTextRendering + }, "EnableVisualStyles=false"), + (new ApplicationConfig + { + DefaultFont = null, + EnableVisualStyles = true, + HighDpiMode = PropertyDefaultValue.DpiMode, + UseCompatibleTextRendering = PropertyDefaultValue.UseCompatibleTextRendering + }, "EnableVisualStyles=true"), + + // UseCompatibleTextRendering: false, true + (new ApplicationConfig + { + DefaultFont = null, + EnableVisualStyles = PropertyDefaultValue.EnableVisualStyles, + HighDpiMode = PropertyDefaultValue.DpiMode, + UseCompatibleTextRendering = false + }, "UseCompTextRendering=false"), + (new ApplicationConfig + { + DefaultFont = null, + EnableVisualStyles = PropertyDefaultValue.EnableVisualStyles, + HighDpiMode = PropertyDefaultValue.DpiMode, + UseCompatibleTextRendering = true + }, "UseCompTextRendering=true"), + + // DefaultFont: null, FontDescriptor + (new ApplicationConfig + { + DefaultFont = null, + EnableVisualStyles = PropertyDefaultValue.EnableVisualStyles, + HighDpiMode = PropertyDefaultValue.DpiMode, + UseCompatibleTextRendering = false + }, "DefaultFont=null"), + (new ApplicationConfig + { + DefaultFont = new FontDescriptor(string.Empty, 12, FontStyle.Bold | FontStyle.Italic, GraphicsUnit.Millimeter), + EnableVisualStyles = PropertyDefaultValue.EnableVisualStyles, + HighDpiMode = PropertyDefaultValue.DpiMode, + UseCompatibleTextRendering = true + }, "DefaultFont=default"), + (new ApplicationConfig + { + DefaultFont = new FontDescriptor("Tahoma", 12, FontStyle.Regular, GraphicsUnit.Point), + EnableVisualStyles = PropertyDefaultValue.EnableVisualStyles, + HighDpiMode = PropertyDefaultValue.DpiMode, + UseCompatibleTextRendering = true + }, "DefaultFont=Tahoma"), + }; + + [Theory] + [MemberData(nameof(GenerateInitializeData))] + public Task ApplicationConfigurationInitializeBuilder_GenerateInitialize((/* ApplicationConfig */object config, string testName) data) + { + string output = ApplicationConfigurationInitializeBuilder.GenerateInitialize(null, (ApplicationConfig)data.config); + + return Verifier.Verify(output) + .UseMethodName("GenerateInitialize") + .UseTextForParameters(data.testName); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_default_boilerplate.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_default_boilerplate.cs new file mode 100644 index 00000000000..75d65e5d049 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_default_boilerplate.cs @@ -0,0 +1,30 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Windows.Forms; + +namespace MyProject +{ + /// + /// Bootstrap the application configuration. + /// + [CompilerGenerated] + internal static partial class ApplicationConfiguration + { + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(false); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.{|CS0117:SetHighDpiMode|}({|CS0103:HighDpiMode|}.PerMonitorV2); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_default_top_level.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_default_top_level.cs new file mode 100644 index 00000000000..6d65d4b4262 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_default_top_level.cs @@ -0,0 +1,32 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(false); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.{|CS0117:SetHighDpiMode|}({|CS0103:HighDpiMode|}.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_user_settings_boilerplate.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_user_settings_boilerplate.cs new file mode 100644 index 00000000000..f2a35cc7e9a --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_user_settings_boilerplate.cs @@ -0,0 +1,32 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Windows.Forms; + +namespace MyProject +{ + /// + /// Bootstrap the application configuration. + /// + [CompilerGenerated] + internal static partial class ApplicationConfiguration + { + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(true); + /// Application.SetDefaultFont(new Font(new FontFamily("Microsoft Sans Serif"), 8.25f, (FontStyle)0, (GraphicsUnit)2)); + /// Application.SetHighDpiMode(HighDpiMode.SystemAware); + /// + /// + public static void Initialize() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(true); + Application.{|CS0117:SetDefaultFont|}(new Font(new FontFamily("Microsoft Sans Serif"), 8.25f, (FontStyle)0, (GraphicsUnit)2)); + Application.{|CS0117:SetHighDpiMode|}({|CS0103:HighDpiMode|}.SystemAware); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_user_top_level.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_user_top_level.cs new file mode 100644 index 00000000000..049d97a3bd6 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationGeneratorTests.GenerateInitialize_user_top_level.cs @@ -0,0 +1,34 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(true); + /// Application.SetDefaultFont(new Font(new FontFamily("Microsoft Sans Serif"), 8.25f, (FontStyle)0, (GraphicsUnit)2)); + /// Application.SetHighDpiMode(HighDpiMode.SystemAware); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(true); + Application.{|CS0117:SetDefaultFont|}(new Font(new FontFamily("Microsoft Sans Serif"), 8.25f, (FontStyle)0, (GraphicsUnit)2)); + Application.{|CS0117:SetHighDpiMode|}({|CS0103:HighDpiMode|}.SystemAware); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationInitializeBuilderTests.default_boilerplate.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationInitializeBuilderTests.default_boilerplate.cs new file mode 100644 index 00000000000..adc69c6eae3 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationInitializeBuilderTests.default_boilerplate.cs @@ -0,0 +1,30 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Windows.Forms; + +namespace MyProject +{ + /// + /// Bootstrap the application configuration. + /// + [CompilerGenerated] + internal static partial class ApplicationConfiguration + { + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(false); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationInitializeBuilderTests.default_top_level.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationInitializeBuilderTests.default_top_level.cs new file mode 100644 index 00000000000..fe67c265d4d --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/MockData/ApplicationConfigurationInitializeBuilderTests.default_top_level.cs @@ -0,0 +1,32 @@ +// + +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows.Forms; + +/// +/// Bootstrap the application configuration. +/// +[CompilerGenerated] +internal static partial class ApplicationConfiguration +{ + /// + /// Bootstrap the application as follows: + /// + /// Application.EnableVisualStyles(); + /// Application.SetCompatibleTextRenderingDefault(false); + /// Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + /// + /// + public static void Initialize() + { + // Set STAThread + Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown); + Thread.CurrentThread.SetApartmentState(ApartmentState.STA); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.FontConverter.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.FontConverter.cs new file mode 100644 index 00000000000..d06cc6c9f6c --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.FontConverter.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Globalization; +using Xunit; +using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Generators.ProjectFileReader; + +namespace System.Windows.Forms.Generators.Tests +{ + public partial class ProjectFileReaderTests + { + public class FontConverterTest + { + private static readonly char s_separator = CultureInfo.CurrentCulture.TextInfo.ListSeparator[0]; + + public static TheoryData TestConvertFormData() + => new() + { + { $"Courier New", "Courier New", PropertyDefaultValue.FontSize, (int)GraphicsUnit.Point, (int)FontStyle.Regular }, + { $"Courier New{s_separator} 11", "Courier New", 11f, (int)GraphicsUnit.Point, (int)FontStyle.Regular }, + { $"Arial{s_separator} 11px", "Arial", 11f, (int)GraphicsUnit.Pixel, (int)FontStyle.Regular }, + { $"Courier New{s_separator} 11 px", "Courier New", 11f, (int)GraphicsUnit.Pixel, (int)FontStyle.Regular }, + { $"Courier New{s_separator} 11 px{s_separator} style=Regular", "Courier New", 11f, (int)GraphicsUnit.Pixel, (int)FontStyle.Regular }, + { $"Courier New{s_separator} style=Bold", "Courier New", PropertyDefaultValue.FontSize, (int)GraphicsUnit.Point, (int)FontStyle.Bold }, + { $"Courier New{s_separator} 11 px{s_separator} style=Bold{s_separator} Italic", "Courier New", 11f, (int)GraphicsUnit.Pixel, (int)(FontStyle.Bold | FontStyle.Italic) }, + { $"Courier New{s_separator} 11 px{s_separator} style=Regular, Italic", "Courier New", 11f, (int)GraphicsUnit.Pixel, (int)(FontStyle.Regular | FontStyle.Italic) }, + { $"Courier New{s_separator} 11 px{s_separator} style=Bold{s_separator} Italic{s_separator} Strikeout", "Courier New", 11f, (int)GraphicsUnit.Pixel, (int)(FontStyle.Bold | FontStyle.Italic | FontStyle.Strikeout) }, + { $"Arial{s_separator} 11 px{s_separator} style=Bold, Italic, Strikeout", "Arial", 11f, (int)GraphicsUnit.Pixel, (int)(FontStyle.Bold | FontStyle.Italic | FontStyle.Strikeout) }, + { $"arIAL{s_separator} 10{s_separator} style=bold", "arIAL", 10f, (int)GraphicsUnit.Point, (int)FontStyle.Bold }, + { $"Arial{s_separator} 10{s_separator}", "Arial", 10f, (int)GraphicsUnit.Point, (int)FontStyle.Regular }, + { $"Arial{s_separator}", "Arial", PropertyDefaultValue.FontSize, (int)GraphicsUnit.Point, (int)FontStyle.Regular }, + { $"Arial{s_separator} 10{s_separator} style=12", "Arial", 10f, (int)GraphicsUnit.Point, (int)(FontStyle.Underline | FontStyle.Strikeout) }, + { $"Courier New{s_separator} Style=Bold", "Courier New", PropertyDefaultValue.FontSize, (int)GraphicsUnit.Point, (int)FontStyle.Bold }, // FullFramework style keyword is case sensitive. + { $"{s_separator} 10{s_separator} style=bold", "", 10f, (int)GraphicsUnit.Point, (int)FontStyle.Bold }, + + // NOTE: in .NET runtime these tests will result in FontName='', but the implementation relies on GDI+, which we don't have... + { $"11px{s_separator} Style=Bold", $"11px", PropertyDefaultValue.FontSize, (int)GraphicsUnit.Point, (int)FontStyle.Bold }, + { $"11px", "11px", PropertyDefaultValue.FontSize, (int)GraphicsUnit.Point, (int)FontStyle.Regular }, + { $"Style=Bold", "Style=Bold", PropertyDefaultValue.FontSize, (int)GraphicsUnit.Point, (int)FontStyle.Regular }, + }; + + [Theory] + [MemberData(nameof(TestConvertFormData))] + internal void TestConvertFrom(string input, string expectedName, float expectedSize, GraphicsUnit expectedUnits, FontStyle expectedFontStyle) + { + FontDescriptor font = FontConverter.ConvertFrom(input)!; + + Assert.Equal(expectedName, font.Name); + Assert.Equal(expectedSize, font.Size); + Assert.Equal(expectedUnits, font.Unit); + Assert.Equal(expectedFontStyle, font.Style); + } + + public static TheoryData ArgumentExceptionFontConverterData() + => new() + { + { $"Courier New{s_separator} 11 px{s_separator} type=Bold{s_separator} Italic" }, + { $"Courier New{s_separator} {s_separator} Style=Bold" }, + { $"Courier New{s_separator} 11{s_separator} Style=" }, + { $"Courier New{s_separator} 11{s_separator} Style=RandomEnum" }, + { $"Arial{s_separator} 10{s_separator} style=bold{s_separator}" }, + { $"Arial{s_separator} 10{s_separator} style=null" }, + { $"Arial{s_separator} 10{s_separator} style=abc#" }, + { $"Arial{s_separator} 10{s_separator} style=##" }, + { $"Arial{s_separator} 10display{s_separator} style=bold" }, + { $"Arial{s_separator} 10style{s_separator} style=bold" }, + }; + + [Theory] + [MemberData(nameof(ArgumentExceptionFontConverterData))] + public void InvalidInputThrowsArgumentException(string input) + { + Assert.Throws(() => FontConverter.ConvertFrom(input)); + } + + public static TheoryData InvalidEnumArgumentExceptionFontConverterData() + => new() + { + { $"Arial{s_separator} 10{s_separator} style=56", "style" }, + { $"Arial{s_separator} 10{s_separator} style=-1", "style" }, + }; + + [Theory] + [MemberData(nameof(InvalidEnumArgumentExceptionFontConverterData))] + public void InvalidInputThrowsInvalidEnumArgumentException(string input, string paramName) + { + Assert.Throws(paramName, () => FontConverter.ConvertFrom(input)); + } + + [Fact] + public void EmptyStringInput() + { + FontDescriptor? font = FontConverter.ConvertFrom(string.Empty); + Assert.Null(font); + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.cs new file mode 100644 index 00000000000..5d58ed770c8 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.cs @@ -0,0 +1,208 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Globalization; +using System.Windows.Forms.Analyzers; +using System.Windows.Forms.Analyzers.Tests; +using System.Windows.Forms.TestUtilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Xunit; +using Xunit.Abstractions; +using static System.Windows.Forms.Generators.ApplicationConfig; + +namespace System.Windows.Forms.Generators.Tests +{ + public partial class ProjectFileReaderTests + { + private static readonly char s_separator = CultureInfo.CurrentCulture.TextInfo.ListSeparator[0]; + private static readonly dynamic s_static = typeof(ProjectFileReader).TestAccessor().Dynamic; + private readonly ITestOutputHelper _output; + + private static bool TryReadBool(AnalyzerConfigOptionsProvider configOptions, string propertyName, bool defaultValue, out bool value, out Diagnostic? diagnostic) + => (bool)s_static.TryReadBool(configOptions, propertyName, defaultValue, out value, out diagnostic); + + private static bool TryReadFont(AnalyzerConfigOptionsProvider configOptions, out FontDescriptor? font, out Diagnostic? diagnostic) + => (bool)s_static.TryReadFont(configOptions, out font, out diagnostic); + + private static bool TryReadHighDpiMode(AnalyzerConfigOptionsProvider configOptions, out HighDpiMode highDpiMode, out Diagnostic? diagnostic) + => (bool)s_static.TryReadHighDpiMode(configOptions, out highDpiMode, out diagnostic); + + public ProjectFileReaderTests(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [InlineData("a")] + [InlineData("@")] + [InlineData("yes")] + [InlineData("no")] + [InlineData("0")] + [InlineData("1")] + [InlineData("-1")] + public void ProjectFileReader_TryReadBool_value_invalid(string value) + { + Dictionary properties = new() + { + { $"build_property.myvalue", value } + }; + CompilerAnalyzerConfigOptions configOptions = new(properties.ToImmutableDictionary()); + CompilerAnalyzerConfigOptionsProvider provider = new(ImmutableDictionary.Empty, configOptions); + + bool result = TryReadBool(provider, "myvalue", defaultValue: false, out bool returnedValue, out Diagnostic? diagnostic); + + Assert.False(result); + Assert.False(returnedValue); + Assert.NotNull(diagnostic); + Assert.Equal(DiagnosticDescriptors.s_propertyCantBeSetToValue, diagnostic!.Descriptor); + _output.WriteLine(diagnostic.ToString()); + } + + [Theory] + [InlineData("", true)] // default value + [InlineData("false", false)] + [InlineData("true", true)] + [InlineData("False", false)] + [InlineData("True", true)] + [InlineData("FALSE", false)] + [InlineData("TRUE", true)] + public void ProjectFileReader_TryReadBool_value_valid(string value, bool expected) + { + Dictionary properties = new() + { + { $"build_property.myvalue", value } + }; + CompilerAnalyzerConfigOptions configOptions = new(properties.ToImmutableDictionary()); + CompilerAnalyzerConfigOptionsProvider provider = new(ImmutableDictionary.Empty, configOptions); + + bool result = TryReadBool(provider, "myvalue", defaultValue: true, out bool returnedValue, out Diagnostic? diagnostic); + + Assert.True(result); + Assert.Equal(expected, returnedValue); + Assert.Null(diagnostic); + } + + public static TheoryData ExceptionFontConverterData() + => new() + { + // ArgumentException + { $"Courier New{s_separator} 11 px{s_separator} type=Bold{s_separator} Italic" }, + { $"Courier New{s_separator} {s_separator} Style=Bold" }, + { $"Courier New{s_separator} 11{s_separator} Style=" }, + { $"Courier New{s_separator} 11{s_separator} Style=RandomEnum" }, + { $"Arial{s_separator} 10{s_separator} style=bold{s_separator}" }, + { $"Arial{s_separator} 10{s_separator} style=null" }, + { $"Arial{s_separator} 10{s_separator} style=abc#" }, + { $"Arial{s_separator} 10{s_separator} style=##" }, + { $"Arial{s_separator} 10display{s_separator} style=bold" }, + { $"Arial{s_separator} 10style{s_separator} style=bold" }, + + // InvalidEnumArgumentException + { $"Arial{s_separator} 10{s_separator} style=56" }, + { $"Arial{s_separator} 10{s_separator} style=-1" }, + }; + + [Theory] + [MemberData(nameof(ExceptionFontConverterData))] + public void ProjectFileReader_TryReadFont_value_invalid(string font) + { + Dictionary properties = new() + { + { $"build_property.{PropertyNameCSharp.DefaultFont}", font } + }; + CompilerAnalyzerConfigOptions configOptions = new(properties.ToImmutableDictionary()); + CompilerAnalyzerConfigOptionsProvider provider = new(ImmutableDictionary.Empty, configOptions); + + bool result = TryReadFont(provider, out FontDescriptor? returnedValue, out Diagnostic? diagnostic); + + Assert.False(result); + Assert.Null(returnedValue); + Assert.NotNull(diagnostic); + Assert.Equal(DiagnosticDescriptors.s_propertyCantBeSetToValueWithReason, diagnostic!.Descriptor); + _output.WriteLine(diagnostic.ToString()); + } + + public static TheoryData TestConvertFormData() + => new() + { + { "Courier New" }, + { $"Arial{s_separator} 11px" }, + { $"Courier New{s_separator} style=Bold" }, + { $"Courier New{s_separator} 11 px{s_separator} style=Regular, Italic" }, + { $"arIAL{s_separator} 10{s_separator} style=bold" }, + { $"Arial{s_separator}" }, + { $"{s_separator} 10{s_separator} style=bold" }, + + // NOTE: in .NET runtime these tests will result in FontName='', but the implementation relies on GDI+, which we don't have... + { $"11px{s_separator} Style=Bold" }, + { $"11px" }, + { $"Style=Bold" }, + }; + + [Theory] + [MemberData(nameof(TestConvertFormData))] + public void ProjectFileReader_TryReadFont_value_valid(string font) + { + Dictionary properties = new() + { + { $"build_property.{PropertyNameCSharp.DefaultFont}", font } + }; + CompilerAnalyzerConfigOptions configOptions = new(properties.ToImmutableDictionary()); + CompilerAnalyzerConfigOptionsProvider provider = new(ImmutableDictionary.Empty, configOptions); + + bool result = TryReadFont(provider, out _, out Diagnostic? diagnostic); + + Assert.True(result); + Assert.Null(diagnostic); + } + + [Theory] + [InlineData("@")] + [InlineData("yes")] + [InlineData("no")] + [InlineData("System")] + [InlineData("10")] + [InlineData("-1")] + [CommonMemberData(typeof(CommonTestHelper), nameof(CommonTestHelper.GetEnumTypeTheoryDataInvalid), typeof(HighDpiMode))] + public void ProjectFileReader_TryReadHighDpiMode_value_invalid(string value) + { + Dictionary properties = new() + { + { $"build_property.{PropertyNameCSharp.HighDpiMode}", value } + }; + CompilerAnalyzerConfigOptions configOptions = new(properties.ToImmutableDictionary()); + CompilerAnalyzerConfigOptionsProvider provider = new(ImmutableDictionary.Empty, configOptions); + + bool result = TryReadHighDpiMode(provider, out HighDpiMode returnedValue, out Diagnostic? diagnostic); + + Assert.False(result); + Assert.Equal(PropertyDefaultValue.DpiMode, returnedValue); + Assert.NotNull(diagnostic); + Assert.Equal(DiagnosticDescriptors.s_propertyCantBeSetToValue, diagnostic!.Descriptor); + _output.WriteLine(diagnostic.ToString()); + } + + [Theory] + [CommonMemberData(typeof(CommonTestHelper), nameof(CommonTestHelper.GetEnumTypeTheoryDataWitIdentity), typeof(HighDpiMode))] +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters + public void ProjectFileReader_TryReadHighDpiMode_value_valid(string value, int counter) +#pragma warning restore xUnit1026 // Theory methods should use all of their parameters + { + Dictionary properties = new() + { + { $"build_property.{PropertyNameCSharp.HighDpiMode}", value } + }; + CompilerAnalyzerConfigOptions configOptions = new(properties.ToImmutableDictionary()); + CompilerAnalyzerConfigOptionsProvider provider = new(ImmutableDictionary.Empty, configOptions); + + bool result = TryReadHighDpiMode(provider, out HighDpiMode returnedValue, out Diagnostic? diagnostic); + + Assert.True(result); + Assert.Equal(Enum.Parse(value, true), returnedValue); + Assert.Null(diagnostic); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/TestAdditionalText.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/TestAdditionalText.cs new file mode 100644 index 00000000000..397808decf1 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/TestAdditionalText.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace System.Windows.Forms.Analyzers.Tests +{ + // Borrowed from https://github.com/dotnet/roslyn/blob/main/src/Compilers/Test/Core/Mocks/TestAdditionalText.cs + + [ExcludeFromCodeCoverage] + public sealed class TestAdditionalText : AdditionalText + { + private readonly SourceText _text; + + public TestAdditionalText(string path, SourceText text) + { + Path = path; + _text = text; + } + + public TestAdditionalText(string text = "", Encoding? encoding = null, string path = "dummy") + : this(path, SourceText.From(text, encoding)) + { + } + + public override string Path { get; } + + public override SourceText GetText(CancellationToken cancellationToken = default) => _text; + } +} + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/Verifiers/CSharpSourceGeneratorTest`1.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/Verifiers/CSharpSourceGeneratorTest`1.cs new file mode 100644 index 00000000000..583d02624ab --- /dev/null +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/Verifiers/CSharpSourceGeneratorTest`1.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public class CSharpSourceGeneratorTest : CSharpSourceGeneratorTest + where TSourceGenerator : ISourceGenerator, new() + { + public CSharpSourceGeneratorTest() + { + ReferenceAssemblies = NetFramework.Net472.WindowsForms; + } + + /// + /// Allows you to specify additional global options that will appear in the context.AnalyzerConfigOptions.GlobalOptions object. + /// + public List<(string, string)> GlobalOptions { get; } = new(); + + protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray sourceGenerators) + => CSharpGeneratorDriver.Create( + sourceGenerators, + project.AnalyzerOptions.AdditionalFiles, + (CSharpParseOptions)project.ParseOptions!, + new OptionsProvider(project.AnalyzerOptions.AnalyzerConfigOptionsProvider, GlobalOptions)); + + /// + /// This class just passes argument through to the projects options provider and it used to provider custom global options + /// + private class OptionsProvider : AnalyzerConfigOptionsProvider + { + private readonly AnalyzerConfigOptionsProvider _analyzerConfigOptionsProvider; + + public OptionsProvider(AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, List<(string, string)> globalOptions) + { + _analyzerConfigOptionsProvider = analyzerConfigOptionsProvider; + GlobalOptions = new ConfigOptions(_analyzerConfigOptionsProvider.GlobalOptions, globalOptions); + } + + public override AnalyzerConfigOptions GlobalOptions { get; } + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) + => _analyzerConfigOptionsProvider.GetOptions(tree); + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + => _analyzerConfigOptionsProvider.GetOptions(textFile); + } + + /// + /// Allows adding additional global options + /// + private class ConfigOptions : AnalyzerConfigOptions + { + private readonly AnalyzerConfigOptions _workspaceOptions; + private readonly Dictionary _globalOptions; + + public ConfigOptions(AnalyzerConfigOptions workspaceOptions, List<(string, string)> globalOptions) + { + _workspaceOptions = workspaceOptions; + _globalOptions = globalOptions.ToDictionary(t => t.Item1, t => t.Item2); + } + + public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) + => _workspaceOptions.TryGetValue(key, out value) || _globalOptions.TryGetValue(key, out value); + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs index 06028daacd9..3ae458173a5 100644 --- a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs +++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs @@ -2,7 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if WINFORMS_ANALYZERS +namespace System.Windows.Forms.Generators +#else namespace System.Windows.Forms +#endif { /// /// Specifies the different high DPI modes that can be applied to an application. diff --git a/src/System.Windows.Forms.PrivateSourceGenerators/src/System.Windows.Forms.PrivateSourceGenerators.csproj b/src/System.Windows.Forms.PrivateSourceGenerators/src/System.Windows.Forms.PrivateSourceGenerators.csproj index 62eb690b7f6..a973da9f153 100644 --- a/src/System.Windows.Forms.PrivateSourceGenerators/src/System.Windows.Forms.PrivateSourceGenerators.csproj +++ b/src/System.Windows.Forms.PrivateSourceGenerators/src/System.Windows.Forms.PrivateSourceGenerators.csproj @@ -1,9 +1,10 @@ - + netstandard2.0 Preview enable + $(NoWarn);RS1024 diff --git a/src/System.Windows.Forms.PrivateSourceGenerators/tests/UnitTests/System.Windows.Forms.PrivateSourceGenerators.Tests.csproj b/src/System.Windows.Forms.PrivateSourceGenerators/tests/UnitTests/System.Windows.Forms.PrivateSourceGenerators.Tests.csproj index bf32c849168..208a5519844 100644 --- a/src/System.Windows.Forms.PrivateSourceGenerators/tests/UnitTests/System.Windows.Forms.PrivateSourceGenerators.Tests.csproj +++ b/src/System.Windows.Forms.PrivateSourceGenerators/tests/UnitTests/System.Windows.Forms.PrivateSourceGenerators.Tests.csproj @@ -1,10 +1,5 @@  - - System.Windows.Forms.PrivateSourceGenerators.Tests - true - - diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs index 9897a4187df..d5af37fe7a3 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. From 4719079f7ba95e5e354a7e4d58d759750916281e Mon Sep 17 00:00:00 2001 From: Igor Velikorossov Date: Wed, 21 Jul 2021 20:41:40 +1000 Subject: [PATCH 2/4] Add general purpose analyzers Add analyzers for C# and VB projects that check whether a user project contains a manifest file, which includes dpi configurations. If such manifest is detected the user is advised to use Application.SetHighDpiMode() API instead. --- Winforms.sln | 38 +++++ eng/Localize/LocProject.json | 5 + ...stem.Windows.Forms.Analyzers.CSharp.csproj | 6 + ...plicationConfigurationInitializeBuilder.cs | 1 + .../ProjectFileReader.FontConverter.cs | 2 +- .../Forms/Generators/ProjectFileReader.cs | 2 +- .../ApplicationConfigurationGeneratorTests.cs | 3 +- ...tionConfigurationInitializeBuilderTests.cs | 3 +- .../ProjectFileReaderTests.FontConverter.cs | 2 +- .../Generators/ProjectFileReaderTests.cs | 4 +- .../AppManifestAnalyzer.Help.md | 2 + .../src/AnalyzerReleases.Shipped.md | 3 + .../src/AnalyzerReleases.Unshipped.md | 7 + .../src/DiagnosticDescriptors.cs | 35 +++++ .../src/Properties/AssemblyInfo.cs | 7 + .../src/Properties/launchSettings.json | 8 + .../src/Resources/SR.resx | 70 +++++++++ .../src/System.Windows.Forms.Analyzers.csproj | 39 +++++ .../src/System.Windows.Forms.Analyzers.props | 3 +- ...AnalyzerConfigOptionsProviderExtensions.cs | 4 +- .../Windows/Forms/AppManifestAnalyzer.cs | 85 +++++++++++ .../ApplicationConfig.FontDescriptor.cs | 4 +- .../Forms}/ApplicationConfig.FontStyle.cs | 4 +- .../Forms}/ApplicationConfig.GraphicsUnit.cs | 4 +- .../Windows/Forms}/ApplicationConfig.cs | 4 +- ...ystem.Windows.Forms.Analyzers.Tests.csproj | 38 +++++ .../Analyzers/AppManifestAnalyzerTests.cs | 143 ++++++++++++++++++ .../ApplicationConfigTests.FontDescriptor.cs | 4 +- .../ApplicationConfigTests.FontStyle.cs | 4 +- .../ApplicationConfigTests.GraphicsUnit.cs | 4 +- .../Analyzers}/ApplicationConfigTests.cs | 2 +- .../Forms/Analyzers/MockData/dpi.manifest | 76 ++++++++++ .../Forms/Analyzers/MockData/invalid.manifest | 45 ++++++ .../Forms/Analyzers/MockData/nodpi.manifest | 76 ++++++++++ .../CSharpAnalyzerVerifier`1+Test.cs | 34 +++++ .../Verifiers/CSharpAnalyzerVerifier`1.cs | 40 +++++ .../Verifiers/CSharpCodeFixVerifier`2+Test.cs | 36 +++++ .../Verifiers/CSharpCodeFixVerifier`2.cs | 63 ++++++++ .../CSharpCodeRefactoringVerifier`1+Test.cs | 34 +++++ .../CSharpCodeRefactoringVerifier`1.cs | 38 +++++ .../Verifiers/CSharpVerifierHelper.cs | 36 +++++ .../VisualBasicAnalyzerVerifier`1+Test.cs | 23 +++ .../VisualBasicAnalyzerVerifier`1.cs | 40 +++++ .../VisualBasicCodeFixVerifier`2+Test.cs | 20 +++ .../Verifiers/VisualBasicCodeFixVerifier`2.cs | 63 ++++++++ ...sualBasicCodeRefactoringVerifier`1+Test.cs | 18 +++ .../VisualBasicCodeRefactoringVerifier`1.cs | 38 +++++ .../src/System/Windows/Forms/HighDpiMode.cs | 2 +- 48 files changed, 1196 insertions(+), 26 deletions(-) create mode 100644 src/System.Windows.Forms.Analyzers/AppManifestAnalyzer.Help.md create mode 100644 src/System.Windows.Forms.Analyzers/src/AnalyzerReleases.Shipped.md create mode 100644 src/System.Windows.Forms.Analyzers/src/AnalyzerReleases.Unshipped.md create mode 100644 src/System.Windows.Forms.Analyzers/src/DiagnosticDescriptors.cs create mode 100644 src/System.Windows.Forms.Analyzers/src/Properties/AssemblyInfo.cs create mode 100644 src/System.Windows.Forms.Analyzers/src/Properties/launchSettings.json create mode 100644 src/System.Windows.Forms.Analyzers/src/Resources/SR.resx create mode 100644 src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj rename src/{System.Windows.Forms.Analyzers.CSharp => System.Windows.Forms.Analyzers}/src/System.Windows.Forms.Analyzers.props (82%) rename src/{System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/src/System/Windows/Forms}/AnalyzerConfigOptionsProviderExtensions.cs (89%) create mode 100644 src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/AppManifestAnalyzer.cs rename src/{System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/src/System/Windows/Forms}/ApplicationConfig.FontDescriptor.cs (91%) rename src/{System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/src/System/Windows/Forms}/ApplicationConfig.FontStyle.cs (90%) rename src/{System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/src/System/Windows/Forms}/ApplicationConfig.GraphicsUnit.cs (92%) rename src/{System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/src/System/Windows/Forms}/ApplicationConfig.cs (92%) create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/System.Windows.Forms.Analyzers.Tests.csproj create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/AppManifestAnalyzerTests.cs rename src/{System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers}/ApplicationConfigTests.FontDescriptor.cs (95%) rename src/{System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers}/ApplicationConfigTests.FontStyle.cs (90%) rename src/{System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers}/ApplicationConfigTests.GraphicsUnit.cs (92%) rename src/{System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators => System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers}/ApplicationConfigTests.cs (83%) create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/dpi.manifest create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/invalid.manifest create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/nodpi.manifest create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpAnalyzerVerifier`1.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeRefactoringVerifier`1.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpVerifierHelper.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicAnalyzerVerifier`1.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeFixVerifier`2.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs create mode 100644 src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs diff --git a/Winforms.sln b/Winforms.sln index 15231e7af04..53bc60ef656 100644 --- a/Winforms.sln +++ b/Winforms.sln @@ -163,6 +163,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiScrollBarTests", "src\S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiMDITests", "src\System.Windows.Forms\tests\IntegrationTests\MauiTests\MauiMDITests\MauiMDITests.csproj", "{5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.Analyzers", "src\System.Windows.Forms.Analyzers\src\System.Windows.Forms.Analyzers.csproj", "{3596BDE6-B211-4BE7-810D-DC7A4315E296}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.Analyzers.Tests", "src\System.Windows.Forms.Analyzers\tests\UnitTests\System.Windows.Forms.Analyzers.Tests.csproj", "{E742382E-5D34-481D-A3FC-7B6B9C5033E2}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{E4C6C5F5-46E9-4C63-9628-26752B4D9C11}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{D3AD0BF9-F5E2-4913-9AE3-9C4998F95EA1}" @@ -951,6 +955,38 @@ Global {5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA}.Release|x64.Build.0 = Release|Any CPU {5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA}.Release|x86.ActiveCfg = Release|Any CPU {5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA}.Release|x86.Build.0 = Release|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Debug|arm64.ActiveCfg = Debug|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Debug|arm64.Build.0 = Debug|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Debug|x64.ActiveCfg = Debug|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Debug|x64.Build.0 = Debug|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Debug|x86.ActiveCfg = Debug|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Debug|x86.Build.0 = Debug|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Release|Any CPU.Build.0 = Release|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Release|arm64.ActiveCfg = Release|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Release|arm64.Build.0 = Release|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Release|x64.ActiveCfg = Release|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Release|x64.Build.0 = Release|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Release|x86.ActiveCfg = Release|Any CPU + {3596BDE6-B211-4BE7-810D-DC7A4315E296}.Release|x86.Build.0 = Release|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Debug|arm64.ActiveCfg = Debug|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Debug|arm64.Build.0 = Debug|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Debug|x64.ActiveCfg = Debug|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Debug|x64.Build.0 = Debug|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Debug|x86.ActiveCfg = Debug|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Debug|x86.Build.0 = Debug|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Release|Any CPU.Build.0 = Release|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Release|arm64.ActiveCfg = Release|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Release|arm64.Build.0 = Release|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Release|x64.ActiveCfg = Release|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Release|x64.Build.0 = Release|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Release|x86.ActiveCfg = Release|Any CPU + {E742382E-5D34-481D-A3FC-7B6B9C5033E2}.Release|x86.Build.0 = Release|Any CPU {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|Any CPU.Build.0 = Debug|Any CPU {5025D7FF-EB6D-4250-B9C5-887ADFBBD952}.Debug|arm64.ActiveCfg = Debug|Any CPU @@ -1044,6 +1080,8 @@ Global {05FD23CE-60AE-44A8-8DD6-1688F04BE385} = {DF68A171-D27B-4E6A-8A7E-63A651622355} {872E41E1-FF66-4B12-A273-1F26A548666F} = {8F20A905-BD37-4D80-B8DF-FA45276FC23F} {5D8C7F71-8B58-48B4-8A54-11E5AB8B02CA} = {8F20A905-BD37-4D80-B8DF-FA45276FC23F} + {3596BDE6-B211-4BE7-810D-DC7A4315E296} = {E4C6C5F5-46E9-4C63-9628-26752B4D9C11} + {E742382E-5D34-481D-A3FC-7B6B9C5033E2} = {D3AD0BF9-F5E2-4913-9AE3-9C4998F95EA1} {E4C6C5F5-46E9-4C63-9628-26752B4D9C11} = {77FEDB47-F7F6-490D-AF7C-ABB4A9E0B9D7} {D3AD0BF9-F5E2-4913-9AE3-9C4998F95EA1} = {DF68A171-D27B-4E6A-8A7E-63A651622355} {5025D7FF-EB6D-4250-B9C5-887ADFBBD952} = {E4C6C5F5-46E9-4C63-9628-26752B4D9C11} diff --git a/eng/Localize/LocProject.json b/eng/Localize/LocProject.json index 31782c25fa0..fd5c9688455 100644 --- a/eng/Localize/LocProject.json +++ b/eng/Localize/LocProject.json @@ -77,6 +77,11 @@ "CopyOption": "LangIDOnName", "OutputPath": ".\\src\\System.Windows.Forms\\tests\\IntegrationTests\\WinformsControlsTest\\xlf\\" }, + { + "SourceFile": ".\\src\\System.Windows.Forms.Analyzers\\src\\Resources\\xlf\\SR.xlf", + "CopyOption": "LangIDOnName", + "OutputPath": ".\\src\\System.Windows.Forms.Analyzers\\src\\Resources\\xlf\\" + }, { "SourceFile": ".\\src\\System.Windows.Forms.Analyzers.CSharp\\src\\Resources\\xlf\\SR.xlf", "CopyOption": "LangIDOnName", diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj index e3998ff9f0d..78f63e64aac 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj @@ -14,7 +14,13 @@ + + + + + + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs index e143c254cb0..05bb8e6f323 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Text; +using System.Windows.Forms.Analyzers; namespace System.Windows.Forms.Generators { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.FontConverter.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.FontConverter.cs index ec614b062ce..36fcb932b2a 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.FontConverter.cs +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.FontConverter.cs @@ -4,7 +4,7 @@ using System.ComponentModel; using System.Globalization; -using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Analyzers.ApplicationConfig; namespace System.Windows.Forms.Generators { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.cs b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.cs index 6ed0f6a178e..d4e56add275 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.cs +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ProjectFileReader.cs @@ -5,7 +5,7 @@ using System.Windows.Forms.Analyzers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Analyzers.ApplicationConfig; namespace System.Windows.Forms.Generators { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationGeneratorTests.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationGeneratorTests.cs index 8193b9ff526..dfcc0cbd1f8 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationGeneratorTests.cs +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationGeneratorTests.cs @@ -3,12 +3,13 @@ // See the LICENSE file in the project root for more information. using System.Text; +using System.Windows.Forms.Analyzers; using System.Windows.Forms.Analyzers.Tests; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Xunit; -using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Analyzers.ApplicationConfig; namespace System.Windows.Forms.Generators.Tests { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.cs index 90f0d841ba6..2250c3e1dc6 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.cs +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilderTests.cs @@ -2,9 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Windows.Forms.Analyzers; using VerifyXunit; using Xunit; -using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Analyzers.ApplicationConfig; namespace System.Windows.Forms.Generators.Tests { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.FontConverter.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.FontConverter.cs index d06cc6c9f6c..8c0d7199b1f 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.FontConverter.cs +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.FontConverter.cs @@ -5,7 +5,7 @@ using System.ComponentModel; using System.Globalization; using Xunit; -using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Analyzers.ApplicationConfig; using static System.Windows.Forms.Generators.ProjectFileReader; namespace System.Windows.Forms.Generators.Tests diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.cs b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.cs index 5d58ed770c8..0a4223a51ff 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.cs +++ b/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ProjectFileReaderTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -11,7 +11,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Xunit; using Xunit.Abstractions; -using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Analyzers.ApplicationConfig; namespace System.Windows.Forms.Generators.Tests { diff --git a/src/System.Windows.Forms.Analyzers/AppManifestAnalyzer.Help.md b/src/System.Windows.Forms.Analyzers/AppManifestAnalyzer.Help.md new file mode 100644 index 00000000000..f80ac6db3fc --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/AppManifestAnalyzer.Help.md @@ -0,0 +1,2 @@ +# How to use AppManifestAnalyzer + diff --git a/src/System.Windows.Forms.Analyzers/src/AnalyzerReleases.Shipped.md b/src/System.Windows.Forms.Analyzers/src/AnalyzerReleases.Shipped.md new file mode 100644 index 00000000000..d567f14248e --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/src/AnalyzerReleases.Shipped.md @@ -0,0 +1,3 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/src/System.Windows.Forms.Analyzers/src/AnalyzerReleases.Unshipped.md b/src/System.Windows.Forms.Analyzers/src/AnalyzerReleases.Unshipped.md new file mode 100644 index 00000000000..879a5225034 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/src/AnalyzerReleases.Unshipped.md @@ -0,0 +1,7 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +WFAC010 | ApplicationConfiguration | Warning | AppManifestAnalyzer, [Documentation](https://github.com/dotnet/winforms/blob/main/src/System.Windows.Forms.Analyzers/AppManifestAnalyzer.Help.md) diff --git a/src/System.Windows.Forms.Analyzers/src/DiagnosticDescriptors.cs b/src/System.Windows.Forms.Analyzers/src/DiagnosticDescriptors.cs new file mode 100644 index 00000000000..a388210801a --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/src/DiagnosticDescriptors.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; + +namespace System.Windows.Forms.Analyzers +{ + internal static class DiagnosticDescriptors + { + private const string Category = "ApplicationConfiguration"; + + private static readonly LocalizableString s_localizableWFAC010Title + = new LocalizableResourceString(nameof(SR.WFAC010Title), SR.ResourceManager, typeof(SR)); + private static readonly LocalizableString s_localizableWFAC010Message_CS + = new LocalizableResourceString(nameof(SR.WFAC010Message_CS), SR.ResourceManager, typeof(SR)); + private static readonly LocalizableString s_localizableWFAC010Message_VB + = new LocalizableResourceString(nameof(SR.WFAC010Message_VB), SR.ResourceManager, typeof(SR)); + + internal static readonly DiagnosticDescriptor s_migrateHighDpiSettings_CSharp + = new(id: "WFAC010", + title: s_localizableWFAC010Title, + messageFormat: s_localizableWFAC010Message_CS, + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + internal static readonly DiagnosticDescriptor s_migrateHighDpiSettings_VB + = new(id: "WFAC010", + title: s_localizableWFAC010Title, + messageFormat: s_localizableWFAC010Message_VB, + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + } +} diff --git a/src/System.Windows.Forms.Analyzers/src/Properties/AssemblyInfo.cs b/src/System.Windows.Forms.Analyzers/src/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..4fb491f70cf --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/src/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("System.Windows.Forms.Analyzers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/System.Windows.Forms.Analyzers/src/Properties/launchSettings.json b/src/System.Windows.Forms.Analyzers/src/Properties/launchSettings.json new file mode 100644 index 00000000000..ed455882673 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/src/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "System.Windows.Forms.Analyzers": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\..\\System.Windows.Forms\\tests\\IntegrationTests\\WinformsControlsTest\\WinformsControlsTest.csproj" + } + } +} \ No newline at end of file diff --git a/src/System.Windows.Forms.Analyzers/src/Resources/SR.resx b/src/System.Windows.Forms.Analyzers/src/Resources/SR.resx new file mode 100644 index 00000000000..7a282f6dddf --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/src/Resources/SR.resx @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unsupported high DPI configuration + + + Remove high DPI settings from {0} and configure via Application.SetHighDpiMode API or '{1}' project property + + + Remove high DPI settings from {0} and configure via '{1}' property in Application Framework + + diff --git a/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj b/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj new file mode 100644 index 00000000000..3edcf9bbaba --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj @@ -0,0 +1,39 @@ + + + + netstandard2.0 + Preview + enable + + true + + + true + WINFORMS_ANALYZERS + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + true + System + + + + + + + + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.props b/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.props similarity index 82% rename from src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.props rename to src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.props index b9625dd51c4..567a9a2bbe9 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.props +++ b/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.props @@ -6,7 +6,8 @@ --> - + + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/AnalyzerConfigOptionsProviderExtensions.cs b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/AnalyzerConfigOptionsProviderExtensions.cs similarity index 89% rename from src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/AnalyzerConfigOptionsProviderExtensions.cs rename to src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/AnalyzerConfigOptionsProviderExtensions.cs index 84abc85c1f1..92ecd1e4fb8 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/AnalyzerConfigOptionsProviderExtensions.cs +++ b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/AnalyzerConfigOptionsProviderExtensions.cs @@ -1,10 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.Diagnostics; -namespace System.Windows.Forms.Generators +namespace System.Windows.Forms.Analyzers { internal static class AnalyzerConfigOptionsProviderExtensions { diff --git a/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/AppManifestAnalyzer.cs b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/AppManifestAnalyzer.cs new file mode 100644 index 00000000000..4dd0cc276e8 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/AppManifestAnalyzer.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Xml; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace System.Windows.Forms.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public partial class AppManifestAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(DiagnosticDescriptors.s_migrateHighDpiSettings_CSharp, + DiagnosticDescriptors.s_migrateHighDpiSettings_VB); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterAdditionalFileAction(AdditionalFileAction); + } + + private void AdditionalFileAction(AdditionalFileAnalysisContext context) + { + if (context.AdditionalFile.Path.EndsWith(".manifest", StringComparison.OrdinalIgnoreCase)) + { + VerifyAppManifest(context, context.AdditionalFile); + } + + // TODO: look for app.config? + } + + private static void VerifyAppManifest(AdditionalFileAnalysisContext context, AdditionalText appManifest) + { + SourceText? appManifestXml = appManifest.GetText(context.CancellationToken); + if (appManifestXml is null) + { + return; + } + + // If the manifest file is corrupt - let the build fail + XmlDocument doc = new(); + try + { + doc.LoadXml(appManifestXml.ToString()); + } + catch + { + // Invalid xml, don't care + return; + } + + XmlNamespaceManager nsmgr = new(doc.NameTable); + nsmgr.AddNamespace("v1", "urn:schemas-microsoft-com:asm.v1"); + nsmgr.AddNamespace("v3", "urn:schemas-microsoft-com:asm.v3"); + nsmgr.AddNamespace("v3ws", "http://schemas.microsoft.com/SMI/2005/WindowsSettings"); + + if (doc.DocumentElement.SelectSingleNode("//v3:application/v3:windowsSettings/v3ws:dpiAware", nsmgr) is not null) + { + switch (context.Compilation.Language) + { + case LanguageNames.CSharp: + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.s_migrateHighDpiSettings_CSharp, + Location.None, + appManifest.Path, + ApplicationConfig.PropertyNameCSharp.HighDpiMode)); + break; + case LanguageNames.VisualBasic: + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.s_migrateHighDpiSettings_VB, + Location.None, + appManifest.Path, + ApplicationConfig.PropertyNameVisualBasic.HighDpiMode)); + break; + default: + throw new NotSupportedException(); + } + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontDescriptor.cs b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.FontDescriptor.cs similarity index 91% rename from src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontDescriptor.cs rename to src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.FontDescriptor.cs index 60e354f38af..c6b6d0cd44a 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontDescriptor.cs +++ b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.FontDescriptor.cs @@ -1,10 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Text.RegularExpressions; -namespace System.Windows.Forms.Generators +namespace System.Windows.Forms.Analyzers { internal partial class ApplicationConfig { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontStyle.cs b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.FontStyle.cs similarity index 90% rename from src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontStyle.cs rename to src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.FontStyle.cs index 61ee92a7573..97877a077fa 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.FontStyle.cs +++ b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.FontStyle.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace System.Windows.Forms.Generators +namespace System.Windows.Forms.Analyzers { internal partial class ApplicationConfig { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.GraphicsUnit.cs b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.GraphicsUnit.cs similarity index 92% rename from src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.GraphicsUnit.cs rename to src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.GraphicsUnit.cs index 254fc4fd552..a436eb2a341 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.GraphicsUnit.cs +++ b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.GraphicsUnit.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace System.Windows.Forms.Generators +namespace System.Windows.Forms.Analyzers { internal partial class ApplicationConfig { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.cs b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.cs similarity index 92% rename from src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.cs rename to src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.cs index 57b4e12e9c2..a5a01c309c4 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfig.cs +++ b/src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/ApplicationConfig.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace System.Windows.Forms.Generators +namespace System.Windows.Forms.Analyzers { internal partial class ApplicationConfig { diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/System.Windows.Forms.Analyzers.Tests.csproj b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System.Windows.Forms.Analyzers.Tests.csproj new file mode 100644 index 00000000000..60cd05a24b5 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System.Windows.Forms.Analyzers.Tests.csproj @@ -0,0 +1,38 @@ + + + + enable + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + Always + + + diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/AppManifestAnalyzerTests.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/AppManifestAnalyzerTests.cs new file mode 100644 index 00000000000..b012d38be69 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/AppManifestAnalyzerTests.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using Xunit; +using VerifyCS = System.Windows.Forms.Analyzers.Tests.CSharpAnalyzerVerifier< + System.Windows.Forms.Analyzers.AppManifestAnalyzer>; +using VerifyVB = System.Windows.Forms.Analyzers.Tests.VisualBasicAnalyzerVerifier< + System.Windows.Forms.Analyzers.AppManifestAnalyzer>; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public class AppManifestAnalyzerTests + { + private const string CSharCode = @" + namespace ConsoleApplication1 + { + class {|#0:TypeName|} + { + } + }"; + private const string VbCode = @" +Namespace ConsoleApplication1 + Class {|#0:TypeName|} + End Class +End Namespace"; + + [Fact] + public async Task AppManifestAnalyzer_noop_if_no_manifest_file() + { + await new VerifyCS.Test + { + TestCode = CSharCode, + TestState = + { + AdditionalFiles = { } + } + }.RunAsync(); + } + + [Fact] + public async Task AppManifestAnalyzer_noop_if_manifest_file_has_no_dpi_info() + { + SourceText manifestFile = SourceText.From(File.ReadAllText(@"System\Windows\Forms\Analyzers\MockData\nodpi.manifest")); + await new VerifyCS.Test + { + TestCode = CSharCode, + TestState = + { + AdditionalFiles = { (@"C:\temp\app.manifest", manifestFile) } + } + }.RunAsync(); + } + + [Fact] + public async Task AppManifestAnalyzer_noop_if_manifest_file_corrupt() + { + SourceText manifestFile = SourceText.From(File.ReadAllText(@"System\Windows\Forms\Analyzers\MockData\invalid.manifest")); + await new VerifyCS.Test + { + TestCode = CSharCode, + TestState = + { + AdditionalFiles = { (@"C:\temp\app.manifest", manifestFile) } + } + }.RunAsync(); + } + + [Fact] + public async Task AppManifestAnalyzer_warn_if_manifest_file_has_dpi_info_CSharp() + { + const string manifestFilePath = @"C:\temp\app.manifest"; + SourceText manifestFile = SourceText.From(File.ReadAllText(@"System\Windows\Forms\Analyzers\MockData\dpi.manifest")); + await new VerifyCS.Test + { + TestCode = CSharCode, + TestState = + { + AdditionalFiles = { (manifestFilePath, manifestFile) } + }, + ExpectedDiagnostics = + { + new DiagnosticResult(DiagnosticDescriptors.s_migrateHighDpiSettings_CSharp) + .WithArguments(manifestFilePath, ApplicationConfig.PropertyNameCSharp.HighDpiMode) + } + }.RunAsync(); + } + + [Fact] + public async Task AppManifestAnalyzer_warn_if_manifest_file_has_dpi_info_VB() + { + const string manifestFilePath = @"C:\temp\app.manifest"; + SourceText manifestFile = SourceText.From(File.ReadAllText(@"System\Windows\Forms\Analyzers\MockData\dpi.manifest")); + await new VerifyVB.Test + { + TestCode = VbCode, + TestState = + { + AdditionalFiles = { (manifestFilePath, manifestFile) } + }, + ExpectedDiagnostics = + { + new DiagnosticResult(DiagnosticDescriptors.s_migrateHighDpiSettings_VB) + .WithArguments(manifestFilePath, ApplicationConfig.PropertyNameVisualBasic.HighDpiMode) + } + }.RunAsync(); + } + + [Fact] + public async Task AppManifestAnalyzer_can_suppressed_if_manifest_file_has_dpi_info_CSharp() + { + const string manifestFilePath = @"C:\temp\app.manifest"; + SourceText manifestFile = SourceText.From(File.ReadAllText(@"System\Windows\Forms\Analyzers\MockData\dpi.manifest")); + await new VerifyCS.Test + { + TestCode = CSharCode, + TestState = + { + AdditionalFiles = { (manifestFilePath, manifestFile) }, + AnalyzerConfigFiles = { ("/.globalconfig", $"is_global = true\r\ndotnet_diagnostic.WFAC010.severity = none") } + } + }.RunAsync(); + } + + [Fact] + public async Task AppManifestAnalyzer_can_suppressed_if_manifest_file_has_dpi_info_VB() + { + const string manifestFilePath = @"C:\temp\app.manifest"; + SourceText manifestFile = SourceText.From(File.ReadAllText(@"System\Windows\Forms\Analyzers\MockData\dpi.manifest")); + await new VerifyVB.Test + { + TestCode = VbCode, + TestState = + { + AdditionalFiles = { (manifestFilePath, manifestFile) }, + AnalyzerConfigFiles = { ("/.globalconfig", $"is_global = true\r\ndotnet_diagnostic.WFAC010.severity = none") } + } + }.RunAsync(); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontDescriptor.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.FontDescriptor.cs similarity index 95% rename from src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontDescriptor.cs rename to src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.FontDescriptor.cs index 228aa471270..64573d0bdd5 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontDescriptor.cs +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.FontDescriptor.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using static System.Windows.Forms.Generators.ApplicationConfig; using Xunit; using Xunit.Abstractions; +using static System.Windows.Forms.Analyzers.ApplicationConfig; -namespace System.Windows.Forms.Analyzers.Generators.Tests +namespace System.Windows.Forms.Analyzers.Tests { public partial class ApplicationConfigTests { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontStyle.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.FontStyle.cs similarity index 90% rename from src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontStyle.cs rename to src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.FontStyle.cs index f94cf54d8a3..b38698f6d94 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.FontStyle.cs +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.FontStyle.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using Xunit; -using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Analyzers.ApplicationConfig; -namespace System.Windows.Forms.Analyzers.Generators.Tests +namespace System.Windows.Forms.Analyzers.Tests { public partial class ApplicationConfigTests { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.GraphicsUnit.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.GraphicsUnit.cs similarity index 92% rename from src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.GraphicsUnit.cs rename to src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.GraphicsUnit.cs index 2cdffab4e49..5c683f9291b 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.GraphicsUnit.cs +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.GraphicsUnit.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using Xunit; -using static System.Windows.Forms.Generators.ApplicationConfig; +using static System.Windows.Forms.Analyzers.ApplicationConfig; -namespace System.Windows.Forms.Analyzers.Generators.Tests +namespace System.Windows.Forms.Analyzers.Tests { public partial class ApplicationConfigTests { diff --git a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.cs similarity index 83% rename from src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.cs rename to src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.cs index d6785c77d47..c5193d493d1 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/tests/UnitTests/System/Windows/Forms/Generators/ApplicationConfigTests.cs +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/ApplicationConfigTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace System.Windows.Forms.Analyzers.Generators.Tests +namespace System.Windows.Forms.Analyzers.Tests { public partial class ApplicationConfigTests { diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/dpi.manifest b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/dpi.manifest new file mode 100644 index 00000000000..ef80698dfef --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/dpi.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/invalid.manifest b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/invalid.manifest new file mode 100644 index 00000000000..e09f1115699 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/invalid.manifest @@ -0,0 +1,45 @@ + + + + + + + + + + + + + diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/nodpi.manifest b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/nodpi.manifest new file mode 100644 index 00000000000..d9bbc1e5f7b --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/System/Windows/Forms/Analyzers/MockData/nodpi.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs new file mode 100644 index 00000000000..812850ee2dc --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + public class Test : CSharpAnalyzerTest + { + public Test() + { + ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.WindowsForms; + + SolutionTransforms.Add((solution, projectId) => + { + CompilationOptions compilationOptions = solution.GetProject(projectId)!.CompilationOptions!; + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpAnalyzerVerifier`1.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpAnalyzerVerifier`1.cs new file mode 100644 index 00000000000..cde85617ab4 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpAnalyzerVerifier`1.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + /// + public static DiagnosticResult Diagnostic() + => CSharpAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpAnalyzerVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs new file mode 100644 index 00000000000..2b387ad0161 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public class Test : CSharpCodeFixTest + { + public Test() + { + ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.WindowsForms; + + SolutionTransforms.Add((solution, projectId) => + { + CompilationOptions? compilationOptions = solution.GetProject(projectId)!.CompilationOptions!; + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs new file mode 100644 index 00000000000..9bfeec036d5 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + /// + public static DiagnosticResult Diagnostic() + => CSharpCodeFixVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpCodeFixVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpCodeFixVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource) + => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs new file mode 100644 index 00000000000..a31210b52ff --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + public class Test : CSharpCodeRefactoringTest + { + public Test() + { + ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.WindowsForms; + + SolutionTransforms.Add((solution, projectId) => + { + CompilationOptions compilationOptions = solution.GetProject(projectId)!.CompilationOptions!; + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeRefactoringVerifier`1.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeRefactoringVerifier`1.cs new file mode 100644 index 00000000000..e1840ae682d --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpCodeRefactoringVerifier`1.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + /// + public static async Task VerifyRefactoringAsync(string source, string fixedSource) + { + await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + { + await VerifyRefactoringAsync(source, new[] { expected }, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpVerifierHelper.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpVerifierHelper.cs new file mode 100644 index 00000000000..59157b253fc --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/CSharpVerifierHelper.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System.Collections.Immutable; + +namespace System.Windows.Forms.Analyzers.Tests +{ + internal static class CSharpVerifierHelper + { + /// + /// By default, the compiler reports diagnostics for nullable reference types at + /// , and the analyzer test framework defaults to only validating + /// diagnostics at . This map contains all compiler diagnostic IDs + /// related to nullability mapped to , which is then used to enable all + /// of these warnings for default validation during analyzer and code fix tests. + /// + internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler(); + + private static ImmutableDictionary GetNullableWarningsFromCompiler() + { + string[] args = { "/warnaserror:nullable" }; + var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory); + var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; + + // Workaround for https://github.com/dotnet/roslyn/issues/41610 + nullableWarnings = nullableWarnings + .SetItem("CS8632", ReportDiagnostic.Error) + .SetItem("CS8669", ReportDiagnostic.Error); + + return nullableWarnings; + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs new file mode 100644 index 00000000000..7c1e9509eec --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class VisualBasicAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + public class Test : VisualBasicAnalyzerTest + { + public Test() + { + ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.WindowsForms; + } + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicAnalyzerVerifier`1.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicAnalyzerVerifier`1.cs new file mode 100644 index 00000000000..2b1600d039f --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicAnalyzerVerifier`1.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class VisualBasicAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + /// + public static DiagnosticResult Diagnostic() + => VisualBasicAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => VisualBasicAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => VisualBasicAnalyzerVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs new file mode 100644 index 00000000000..16080c2d472 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class VisualBasicCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public class Test : VisualBasicCodeFixTest + { + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeFixVerifier`2.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeFixVerifier`2.cs new file mode 100644 index 00000000000..980d4282f2a --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeFixVerifier`2.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class VisualBasicCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + /// + public static DiagnosticResult Diagnostic() + => VisualBasicCodeFixVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => VisualBasicCodeFixVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => VisualBasicCodeFixVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource) + => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs new file mode 100644 index 00000000000..b488647ce89 --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class VisualBasicCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + public class Test : VisualBasicCodeRefactoringTest + { + } + } +} diff --git a/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs new file mode 100644 index 00000000000..c6d9240dbff --- /dev/null +++ b/src/System.Windows.Forms.Analyzers/tests/UnitTests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace System.Windows.Forms.Analyzers.Tests +{ + public static partial class VisualBasicCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + /// + public static async Task VerifyRefactoringAsync(string source, string fixedSource) + { + await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + { + await VerifyRefactoringAsync(source, new[] { expected }, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs index 3ae458173a5..e0c0950b25d 100644 --- a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs +++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/HighDpiMode.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. #if WINFORMS_ANALYZERS -namespace System.Windows.Forms.Generators +namespace System.Windows.Forms.Analyzers #else namespace System.Windows.Forms #endif From 88480017bc85ec085f2b19acfc18f9ece48e1a50 Mon Sep 17 00:00:00 2001 From: Igor Velikorossov Date: Mon, 26 Jul 2021 16:47:24 +1000 Subject: [PATCH 3/4] Package analyzers --- azure-pipelines.yml | 1 + eng/packageContent.targets | 13 ++++- .../Directory.Build.targets | 11 +++-- .../Microsoft.Private.Winforms.csproj | 16 ++++--- pkg/Microsoft.Private.Winforms/README.md | 48 +++++++++++++------ ...em.Windows.Forms.FileClassification.props} | 6 +++ .../UpdateFileClassification.ps1} | 33 +++++++------ ....NET.Sdk.WindowsDesktop.WindowsForms.props | 3 ++ .../System.Windows.Forms.Analyzers.props | 29 +++++++++++ ...stem.Windows.Forms.Analyzers.CSharp.csproj | 4 +- .../src/System.Windows.Forms.Analyzers.csproj | 7 +-- 11 files changed, 123 insertions(+), 48 deletions(-) rename pkg/Microsoft.Private.Winforms/{FrameworkListFiles.props => sdk/dotnet-windowsdesktop/System.Windows.Forms.FileClassification.props} (71%) rename pkg/Microsoft.Private.Winforms/{ProcessFrameworkListFiles.ps1 => sdk/dotnet-windowsdesktop/UpdateFileClassification.ps1} (78%) create mode 100644 pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/System.Windows.Forms.Analyzers.props diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4b55af7d354..5b8671d4a91 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,6 +6,7 @@ trigger: - main - release/* - internal/release/* +- internal/experimental/* # trigger ci builds on pull requests into main and any release branches pr: diff --git a/eng/packageContent.targets b/eng/packageContent.targets index ebf42c37cac..0d2a30a1fdf 100644 --- a/eng/packageContent.targets +++ b/eng/packageContent.targets @@ -10,9 +10,18 @@ true + + + <_AnalyzerTargetLanguage>$(AssemblyName.Substring($(AssemblyName.LastIndexOf('.')))) + /cs + /vb + + lib/$(TargetFramework) lib/$(TargetFramework);$(RefPackagePath) + + sdk/analyzers/dotnet$(AnalyzerTargetLanguage) @@ -30,7 +39,7 @@ $([System.IO.Path]::ChangeExtension('$(TargetRefPath)', '.xml')) $([System.IO.Path]::ChangeExtension('$(TargetPath)', '.xml')) - $([System.IO.Path]::GetDirectoryName('$(IntellisenseXml)')) + $([System.IO.Path]::GetDirectoryName('$(IntellisenseXmlDest)')) @@ -39,7 +48,7 @@ This means we build a real assembly that has no associated official intellisense docs. Contact the intellisense team for guidance. --> - diff --git a/pkg/Microsoft.Private.Winforms/Directory.Build.targets b/pkg/Microsoft.Private.Winforms/Directory.Build.targets index 2b04482ea39..f9ecdbc4fab 100644 --- a/pkg/Microsoft.Private.Winforms/Directory.Build.targets +++ b/pkg/Microsoft.Private.Winforms/Directory.Build.targets @@ -4,25 +4,28 @@ <_PowerShellExe Condition="'$(_PowerShellExe)' == ''">C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe - <_ScriptLocation Condition="'$(_ScriptLocation)' == ''">$(MSBuildProjectDirectory)\ProcessFrameworkListFiles.ps1 - <_ManifestFile>$(MSBuildProjectDirectory)\FrameworkListFiles.props + <_ScriptLocation Condition="'$(_ScriptLocation)' == ''">$(MSBuildProjectDirectory)\sdk\dotnet-windowsdesktop\UpdateFileClassification.ps1 + <_ManifestFile>$(MSBuildProjectDirectory)\sdk\dotnet-windowsdesktop\System.Windows.Forms.FileClassification.props false - <_NuspecFile Include="@(NuGetPackOutput)" Condition="'%(Extension)' == '.nuspec'" /> + + + - + diff --git a/pkg/Microsoft.Private.Winforms/Microsoft.Private.Winforms.csproj b/pkg/Microsoft.Private.Winforms/Microsoft.Private.Winforms.csproj index fa87dc0a12e..58ae9d6ab88 100644 --- a/pkg/Microsoft.Private.Winforms/Microsoft.Private.Winforms.csproj +++ b/pkg/Microsoft.Private.Winforms/Microsoft.Private.Winforms.csproj @@ -17,7 +17,7 @@ $(TargetFrameworkName)$(TargetFrameworkVersion) - $(NoWarn);NU5131;NU5128 + $(NoWarn);NU5100;NU5131;NU5128 @@ -52,14 +52,12 @@ + - + True - + sdk\dotnet-windowsdesktop - - - True sdk\dotnet-wpf @@ -98,6 +96,12 @@ + + + + diff --git a/pkg/Microsoft.Private.Winforms/README.md b/pkg/Microsoft.Private.Winforms/README.md index 3059b17f27b..ef6ab5b2acf 100644 --- a/pkg/Microsoft.Private.Winforms/README.md +++ b/pkg/Microsoft.Private.Winforms/README.md @@ -1,33 +1,53 @@ ## Overview -This is a transport package consumed by [WPF](https://github.com/dotnet/wpf/) and [WindowsDesktop](https://github.com/dotnet/windowsdesktop/). +This is a transport package consumed by [WPF](https://github.com/dotnet/wpf/) and [WindowsDesktop](https://github.com/dotnet/windowsdesktop/). Some packaging configurations are defined in this project, and others are defined in \\eng\packageContent.targets. -WindowsDesktop relies on [`FrameworkListFiles.props` manifest](FrameworkListFiles.props) to list all our assemblies that form "WindowsForms" SDK[¹](#ref1). -The props file is then imported by [WindowsDesktop projects](https://github.com/dotnet/windowsdesktop/blob/master/pkg/windowsdesktop/pkg/Directory.Build.props). +## `sdk\dotnet-windowsdesktop` folder + +This folder contains props and targets used to ingest our assemblies into the [Windows Desktop SDK](https://github.com/dotnet/windowsdesktop/) for purpose of bundling of our analyzers into Microsoft.WindowsDesktop.App.Ref pack. + +* [`System.Windows.Forms.FileClassification.props`](sdk\dotnet-windowsdesktop\System.Windows.Forms.FileClassification.props) contains a manifest for the "WindowsForms" SDK[¹](#ref1), i.e. a list of our assemblies that form it. +The file is imported by [Microsoft.WindowsDesktop.App.Ref project](https://github.com/dotnet/windowsdesktop/blob/main/pkg/windowsdesktop/sfx/Microsoft.WindowsDesktop.App.Ref.sfxproj).
The manifest will need to be rebuilt if there are changes in the solution with respect to the SDK assemblies, e.g. a new assembly is added or an existing assembly is removed. -## How it works +### How to update System.Windows.Forms.FileClassification.props -[`FrameworkListFiles.props`](FrameworkListFiles.props) will be compared against the list of assemblies in `Microsoft.Private.Winforms.[version].nuspec`[²](#ref2) generated as part of the `pack` command. +The existing `System.Windows.Forms.FileClassification.props` is be compared against the list of assemblies in `Microsoft.Private.Winforms.[version].nuspec`[²](#ref2) generated as part of the `pack` command. If the content of these files differ - the build will fail. :warning: The process is purposefully made manual to ensure changes in the manifest are made consciously. -## How to update the manifest +To update the manifest run the following command and check in the updated files manifest: + +``` +.\build.cmd -pack /p:GenerateManifest=true +``` + +To debug the script run the following command: + +``` +dotnet build .\pkg\Microsoft.Private.Winforms\Microsoft.Private.Winforms.csproj /t:UpdateTransportPackage /p:GenerateManifest=true /v:m /bl /p:CommonLibrary_NativeInstallDir=$env:UserProfile\.netcoreeng\native\ +``` + + + + +## `sdk\dotnet-wpf` folder + +This folder contains props and targets that are part of [Windows Desktop SDK](https://github.com/dotnet/wpf/blob/main/packaging/Microsoft.NET.Sdk.WindowsDesktop/) (which is hosted and assembled in [dotnet/wpf](https://github.com/dotnet/wpf/)). +These files are referenced the [Microsoft.NET.Sdk.WindowsDesktop project](https://github.com/dotnet/wpf/blob/main/packaging/Microsoft.NET.Sdk.WindowsDesktop/Microsoft.NET.Sdk.WindowsDesktop.ArchNeutral.csproj)'s props and targets located [here](https://github.com/dotnet/wpf/blob/main/packaging/Microsoft.NET.Sdk.WindowsDesktop/targets). When this project is being built, it copies the files from our transport NuGet package to a Microsoft.NET.Sdk.WindowsDesktop bundle. + +* [`Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.props`](sdk\dotnet-wpf\Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.props) contains various Windows Forms specific configurations, such as our default `using` imports. -* Build the solution as normal: - ``` - .\build.cmd - ``` -* To update the manifest [`FrameworkListFiles.props`](FrameworkListFiles.props) run the following command from a developer prompt: - ``` - msbuild .\pkg\Microsoft.Private.Winforms\Microsoft.Private.Winforms.csproj /t:UpdateTransportPackage /p:GenerateManifest=true /v:m - ``` +* [`Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.targets`](sdk\dotnet-wpfp\Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.targets) contains various Windows Forms specific targets. +* [`System.Windows.Forms.Analyzers.props`](sdk\dotnet-wpf\System.Windows.Forms.Analyzers.props) contains a list of properties required by our source generators. +
+
---- diff --git a/pkg/Microsoft.Private.Winforms/FrameworkListFiles.props b/pkg/Microsoft.Private.Winforms/sdk/dotnet-windowsdesktop/System.Windows.Forms.FileClassification.props similarity index 71% rename from pkg/Microsoft.Private.Winforms/FrameworkListFiles.props rename to pkg/Microsoft.Private.Winforms/sdk/dotnet-windowsdesktop/System.Windows.Forms.FileClassification.props index accd13f16a8..1b9051de847 100644 --- a/pkg/Microsoft.Private.Winforms/FrameworkListFiles.props +++ b/pkg/Microsoft.Private.Winforms/sdk/dotnet-windowsdesktop/System.Windows.Forms.FileClassification.props @@ -1,3 +1,7 @@ + @@ -6,6 +10,8 @@ + + diff --git a/pkg/Microsoft.Private.Winforms/ProcessFrameworkListFiles.ps1 b/pkg/Microsoft.Private.Winforms/sdk/dotnet-windowsdesktop/UpdateFileClassification.ps1 similarity index 78% rename from pkg/Microsoft.Private.Winforms/ProcessFrameworkListFiles.ps1 rename to pkg/Microsoft.Private.Winforms/sdk/dotnet-windowsdesktop/UpdateFileClassification.ps1 index c5b0471d56a..2d0e183338d 100644 --- a/pkg/Microsoft.Private.Winforms/ProcessFrameworkListFiles.ps1 +++ b/pkg/Microsoft.Private.Winforms/sdk/dotnet-windowsdesktop/UpdateFileClassification.ps1 @@ -9,24 +9,24 @@ Param( [Parameter(ValueFromRemainingArguments=$true)][String[]] $properties ) -$assemblies = @( ); -# this isn't explicitly present in the list -$assemblies += 'System.Drawing.Common.dll'; [xml] $xmlDoc = Get-Content -Path $NuspecFile -Force; -$xmlDoc.package.files.file | ` + +$assemblies = $xmlDoc.package.files.file | ` Where-Object { # take only assemblies placed in \lib\netcoreappX.Y, and that are not resources # also exclude Accessibility.dll as it is explicitly added to WindowsDesktop bundle - $_.target.StartsWith('lib\') ` + ($_.target.StartsWith('lib\') -or $_.target.StartsWith('ref\') -or $_.target.StartsWith('sdk\analyzers\'))` -and $_.target.EndsWith('.dll', [System.StringComparison]::OrdinalIgnoreCase) ` -and !$_.target.EndsWith('resources.dll', [System.StringComparison]::OrdinalIgnoreCase) ` -and !$_.target.EndsWith('\Accessibility.dll', [System.StringComparison]::OrdinalIgnoreCase) } | ` - ForEach-Object { - $assembly = Split-Path $_.target -Leaf; - $assemblies += $assembly; - }; + Select-Object -Unique @{Name="Path";Expression={Split-Path $_.target -Leaf}} | ` + Select-Object -ExpandProperty Path; + +# this isn't explicitly present in the list +$assemblies += 'System.Drawing.Common.dll'; + $needGenerate = $null; [bool]::TryParse($GenerateManifest, [ref]$needGenerate) | Out-Null; @@ -57,17 +57,20 @@ else { Update the existing manifest #> Write-Host "Regenerating the manifest" -ForegroundColor Green - $output = " - - "; + + $output = " + + `r`n"; $assemblies | ` Sort-Object | ` ForEach-Object { $assembly = $_; - $output += " - " + $output += " `r`n" } $output += " - "; +"; $output | Out-File -FilePath $TargetFile -Encoding utf8 -Force; } diff --git a/pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.props b/pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.props index 4f6bbf02328..6c022cc3207 100644 --- a/pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.props +++ b/pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/Microsoft.NET.Sdk.WindowsDesktop.WindowsForms.props @@ -24,4 +24,7 @@ + + + diff --git a/pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/System.Windows.Forms.Analyzers.props b/pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/System.Windows.Forms.Analyzers.props new file mode 100644 index 00000000000..6b92891c167 --- /dev/null +++ b/pkg/Microsoft.Private.Winforms/sdk/dotnet-wpf/System.Windows.Forms.Analyzers.props @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj index 78f63e64aac..504c682a10d 100644 --- a/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj +++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj @@ -9,7 +9,7 @@ true - true + true WINFORMS_ANALYZERS @@ -20,7 +20,7 @@ - +
diff --git a/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj b/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj index 3edcf9bbaba..f39b4bfce8b 100644 --- a/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj +++ b/src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj @@ -8,12 +8,13 @@ true - true + true WINFORMS_ANALYZERS + @@ -32,8 +33,4 @@ - - - -
From 329e453b2346f17af56dbfb192ecbd81c7e69458 Mon Sep 17 00:00:00 2001 From: Igor Velikorossov Date: Thu, 3 Jun 2021 18:00:26 +1000 Subject: [PATCH 4/4] Local testing --- Directory.Build.props | 7 ++- .../WinformsControlsTest/Program.cs | 47 ++++++------------- .../WinformsControlsTest.csproj | 22 +++++++-- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5e526a38f57..9e43a25628b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - + @@ -17,6 +17,11 @@ true + + + Calibri, 11pt, style=regular + + false diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs index d5af37fe7a3..c8c70ec5c0d 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Program.cs @@ -1,40 +1,23 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Drawing; using System.Windows.Forms; +using WinformsControlsTest; -namespace WinformsControlsTest -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); - - //Application.SetDefaultFont(new Font(new FontFamily("Microsoft Sans Serif"), 8f)); - //Application.SetDefaultFont(new Font(new FontFamily("Chiller"), 12f)); - Application.SetDefaultFont(new Font(new FontFamily("Calibri"), 11f)); +ApplicationConfiguration.Initialize(); - Application.SetCompatibleTextRenderingDefault(false); - Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); //UnhandledExceptionMode.ThrowException - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; - try - { - Application.Run(new MainForm()); - } - catch (System.Exception) - { - Environment.Exit(-1); - } +Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); +Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; - Environment.Exit(0); - } - } +try +{ + Application.Run(new MainForm()); } +catch (Exception) +{ + Environment.Exit(-1); +} + +Environment.Exit(0); + diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/WinformsControlsTest.csproj b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/WinformsControlsTest.csproj index 6e862d82412..7eaa87a7fb6 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/WinformsControlsTest.csproj +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/WinformsControlsTest.csproj @@ -12,10 +12,6 @@ true - - NETCORE - - @@ -23,7 +19,6 @@ - WinformsControlsTest.SmallA.bmp @@ -53,4 +48,21 @@ + + + + false + + + + + + + + +