From b1733e95c6d35b551fc8cf6fe04e2a0c287346dd Mon Sep 17 00:00:00 2001
From: Sophia Chen <sophia.s.chen@vanderbilt.edu>
Date: Fri, 6 Dec 2024 17:23:02 -0800
Subject: [PATCH] Improved Diagnostics for error scenarios with authoring
 projects (#1863)

* first commit

* first commit

* return instead of throw

* dont hardcode version num

* delete unused line

---------

Co-authored-by: Sophia Chen <sophia.six.chen@gmail.com>
Co-authored-by: Manodasan Wignarajah <mawign@microsoft.com>
---
 .../AnalyzerReleases.Shipped.md               |  9 ++-
 .../CsWinRTDiagnosticStrings.Designer.cs      | 18 +++++
 .../CsWinRTDiagnosticStrings.resx             | 73 ++++++++++++++++++-
 .../WinRT.SourceGenerator/Generator.cs        | 10 ++-
 .../WinRT.SourceGenerator/WinRTRules.cs       |  5 ++
 src/cswinrt/main.cpp                          |  4 +-
 src/cswinrt/text_writer.h                     |  8 ++
 7 files changed, 118 insertions(+), 9 deletions(-)

diff --git a/src/Authoring/WinRT.SourceGenerator/AnalyzerReleases.Shipped.md b/src/Authoring/WinRT.SourceGenerator/AnalyzerReleases.Shipped.md
index 95bda0d96..82f5c6bee 100644
--- a/src/Authoring/WinRT.SourceGenerator/AnalyzerReleases.Shipped.md
+++ b/src/Authoring/WinRT.SourceGenerator/AnalyzerReleases.Shipped.md
@@ -47,4 +47,11 @@ CsWinRT1029 | Usage | Warning | Class implements WinRT interfaces generated usin
 ### New Rules
 Rule ID | Category | Severity | Notes
 --------|----------|----------|-------
-CsWinRT1030 | Usage | Warning | Project needs to be updated with `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` to allow generic interface code generation.
+CsWinRT1030 | Usage | Warning | Project needs to be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>' to allow generic interface code generation.
+
+## Release 2.2.1
+
+### New Rules
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+CsWinRT1031 | Usage | Error | Source generator failed.
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.Designer.cs b/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.Designer.cs
index 65d770159..9678479aa 100644
--- a/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.Designer.cs
+++ b/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.Designer.cs
@@ -573,6 +573,24 @@ internal static string RefParameterFound_Text {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Source generator failed.
+        /// </summary>
+        internal static string SourceGeneratorFailed_Brief {
+            get {
+                return ResourceManager.GetString("SourceGeneratorFailed_Brief", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to CsWinRT component authoring source generator &apos;WinRT.SourceGenerator&apos; failed to generate WinMD and projection because of &apos;{0}&apos;.
+        /// </summary>
+        internal static string SourceGeneratorFailed_Text {
+            get {
+                return ResourceManager.GetString("SourceGeneratorFailed_Text", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Const field in struct.
         /// </summary>
diff --git a/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.resx b/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.resx
index 6c1d7f141..58b027ef6 100644
--- a/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.resx
+++ b/src/Authoring/WinRT.SourceGenerator/CsWinRTDiagnosticStrings.resx
@@ -1,5 +1,64 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
     <xsd:element name="root" msdata:IsDataSet="true">
@@ -120,7 +179,7 @@
   </data>
   <data name="BindableCustomPropertyClassNotMarkedPartial_Text" xml:space="preserve">
     <value>Class '{0}' has attribute GeneratedBindableCustomProperty but it or a parent type isn't marked partial.  Type and any parent types should be marked partial to allow source generation for trimming and AOT compatibility.</value>
-  </data>	
+  </data>
   <data name="DisjointNamespaceRule_Brief" xml:space="preserve">
     <value>Namespace is disjoint from main (winmd) namespace</value>
   </data>
@@ -231,9 +290,6 @@
   <data name="RefParameterFound_Brief" xml:space="preserve">
     <value>Parameter passed by reference</value>
   </data>
-  <data name="RefParameterFound_Text" xml:space="preserve">
-    <value>Method '{0}' has parameter '{1}' marked `ref`; reference parameters are not allowed in Windows Runtime</value>
-  </data>
   <data name="StructHasConstFieldRule_Brief" xml:space="preserve">
     <value>Const field in struct</value>
   </data>
@@ -292,4 +348,13 @@
     <value>Consider changing the type '{1} in the member signature to one of the following types from System.Collections.Generic: {2}.</value>
     <comment>{1} and {2} will be keywords (types) from DotNet</comment>
   </data>
+  <data name="RefParameterFound_Text" xml:space="preserve">
+    <value>Method '{0}' has parameter '{1}' marked `ref`; reference parameters are not allowed in Windows Runtime</value>
+  </data>
+  <data name="SourceGeneratorFailed_Brief" xml:space="preserve">
+    <value>Source generator failed</value>
+  </data>
+  <data name="SourceGeneratorFailed_Text" xml:space="preserve">
+    <value>CsWinRT component authoring source generator 'WinRT.SourceGenerator' failed to generate WinMD and projection because of '{0}'</value>
+  </data>
 </root>
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator/Generator.cs b/src/Authoring/WinRT.SourceGenerator/Generator.cs
index c2e159f77..cd7769c37 100644
--- a/src/Authoring/WinRT.SourceGenerator/Generator.cs
+++ b/src/Authoring/WinRT.SourceGenerator/Generator.cs
@@ -14,6 +14,7 @@
 using System.Reflection.PortableExecutable;
 using System.Text;
 using System.Threading;
+using WinRT.SourceGenerator;
 
 namespace Generator
 {
@@ -91,12 +92,15 @@ private void GenerateSources()
             {
                 using var cswinrtProcess = Process.Start(processInfo);
                 Logger.Log(cswinrtProcess.StandardOutput.ReadToEnd());
-                Logger.Log(cswinrtProcess.StandardError.ReadToEnd());
+                var error = cswinrtProcess.StandardError.ReadToEnd();
+                Logger.Log(error);
                 cswinrtProcess.WaitForExit();
 
                 if (cswinrtProcess.ExitCode != 0)
                 {
-                    throw new Win32Exception(cswinrtProcess.ExitCode);
+                    var diagnosticDescriptor = WinRTRules.SourceGeneratorFailed;
+                    context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, null, error));
+                    throw new Win32Exception(cswinrtProcess.ExitCode, error);
                 }
 
                 foreach (var file in Directory.GetFiles(outputDir, "*.cs", SearchOption.TopDirectoryOnly))
@@ -190,7 +194,7 @@ public void Generate()
                 }
                 Logger.Close();
                 Environment.ExitCode = -2;
-                throw;
+                return;
             }
 
             Logger.Log("Done");
diff --git a/src/Authoring/WinRT.SourceGenerator/WinRTRules.cs b/src/Authoring/WinRT.SourceGenerator/WinRTRules.cs
index 5ab4a338f..ca54ee962 100644
--- a/src/Authoring/WinRT.SourceGenerator/WinRTRules.cs
+++ b/src/Authoring/WinRT.SourceGenerator/WinRTRules.cs
@@ -238,5 +238,10 @@ private static DiagnosticDescriptor MakeRule(string id, string title, string mes
             CsWinRTDiagnosticStrings.ClassImplementsOldProjection_Brief,
             CsWinRTDiagnosticStrings.ClassOldProjectionMultipleInstances_Text,
             false);
+
+        public static DiagnosticDescriptor SourceGeneratorFailed = MakeRule(
+            "CsWinRT1031",
+            CsWinRTDiagnosticStrings.SourceGeneratorFailed_Brief,
+            CsWinRTDiagnosticStrings.SourceGeneratorFailed_Text);
     }
 } 
diff --git a/src/cswinrt/main.cpp b/src/cswinrt/main.cpp
index 942d5bbf7..6496ce1d3 100644
--- a/src/cswinrt/main.cpp
+++ b/src/cswinrt/main.cpp
@@ -350,7 +350,7 @@ Where <spec> is one or more of:
                     {
                         writer console;
                         console.write("error: '%' when processing %%%\n", e.what(), ns, currentType.empty() ? "" : ".", currentType);
-                        console.flush_to_console();
+                        console.flush_to_console_error();
                         throw;
                     }
                 });
@@ -541,6 +541,8 @@ ComWrappersSupport.RegisterAuthoringMetadataTypeLookup(new Func<Type, Type>(GetM
         {
             w.write(" error: %\n", e.what());
             result = 1;
+            w.flush_to_console_error();
+            return result;
         }
 
         w.flush_to_console();
diff --git a/src/cswinrt/text_writer.h b/src/cswinrt/text_writer.h
index ce7fee38a..cb70361a9 100644
--- a/src/cswinrt/text_writer.h
+++ b/src/cswinrt/text_writer.h
@@ -153,6 +153,14 @@ namespace cswinrt
             m_second.clear();
         }
 
+        void flush_to_console_error() noexcept
+        {
+            fprintf(stderr, "%.*s", static_cast<int>(m_first.size()), m_first.data());
+            fprintf(stderr, "%.*s", static_cast<int>(m_second.size()), m_second.data());
+            m_first.clear();
+            m_second.clear();
+        }
+
         void flush_to_file(std::string const& filename)
         {
             if (!file_equal(filename))