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/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/Winforms.sln b/Winforms.sln
index 3c29397fe7f..53bc60ef656 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,18 @@ 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}"
+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 +731,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 +955,70 @@ 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
+ {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 +1080,12 @@ 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}
+ {714EC82C-D4E0-4E14-9C34-4F270774D384} = {D3AD0BF9-F5E2-4913-9AE3-9C4998F95EA1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7B1B0433-F612-4E5A-BE7E-FCF5B9F6E136}
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/Localize/LocProject.json b/eng/Localize/LocProject.json
index dfe4e1bdd07..fd5c9688455 100644
--- a/eng/Localize/LocProject.json
+++ b/eng/Localize/LocProject.json
@@ -77,6 +77,16 @@
"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",
+ "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/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/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..504c682a10d
--- /dev/null
+++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj
@@ -0,0 +1,41 @@
+
+
+
+ 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/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..05bb8e6f323
--- /dev/null
+++ b/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs
@@ -0,0 +1,102 @@
+// 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;
+
+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..36fcb932b2a
--- /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.Analyzers.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..d4e56add275
--- /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.Analyzers.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