diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9c3a633f9..1351bbb22 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,38 +4,52 @@
Global references are included in ALL projects in this repository
-->
-
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+ -->
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
-
+
@@ -46,9 +60,9 @@
-
-
-
+
+
+
diff --git a/docfx/ReadMe.md b/docfx/ReadMe.md
index 2a9a14e43..4a5dfcdfa 100644
--- a/docfx/ReadMe.md
+++ b/docfx/ReadMe.md
@@ -78,11 +78,12 @@ Since this is generated it is listed in the [.gitignore](#gitignore) file.
These folders (named after the `*` portion of the [api-*](#api-*) folder names contains
manually written additional files, articles, samples etc... related to a given library.
-## Guid to wrting XML DOC comments
-When dealing with doc comments the XML can get in the way of general readability of the
-source code. There is an inherent tension beween how a particular editor renders the docs
-for a symbol/method (VS calls this "Quick Info") and how it is rendered in the final
-documentation by docfx. This guides general use to simplify things as much as possible.
+## Guide to wrting XML DOC comments
+When dealing with doc comments the XML can sometimes get in the way of general readability
+of the source code. There is an inherent tension beween how a particular editor renders the
+docs for a symbol/method (VS calls this "Quick Info") and how it is rendered in the final
+documentation by a tool like docfx. This guides general use to simplify things as much as
+possible.
### Lists
The largest intrusion of the XML into the source is that of lists. The XML doc comments
@@ -115,7 +116,6 @@ versus:
/// 3) Act on the results as proper for the application
/// a. This might include actions parsed but generally isolating the various stages is an easier to understand/maintain model
/// b. Usually this is just app specific code that uses the bound results to adapt behavior
-///
```
Which one would ***YOU*** rather encounter in code? Which one is easier to understand when
@@ -126,7 +126,7 @@ should reconsider... :grinning:)
There is little that can be done to alter the rendering of any editor support, at most an
editor might allow specification of a CSS file, but that is the lowest priority of doc
comments. Readability by maintainers of the docs AND the rendering for final docs used by
-consumers of of VASTLY higher importance. Still, the editor rendering ***is*** of value to
+consumers is of VASTLY higher importance. Still, the editor rendering ***is*** of value to
maintainers so should not be forgotten as it can make a "right mess of things" even if they
render properly in final docs.
@@ -135,8 +135,8 @@ render properly in final docs.
a) Doing so will break the docfx rendering that allows for markdown lists
2) Use `' tags to indicate a line break. This is used by the editor rendering to mark
the end of a line and start a new one. (Stops auto reflow)
-3) Accept that the in edotr rendering might "trim" the lines it shows, eliminating any
- indentation.
+3) Accept that the in editor rendering might "trim" the lines it shows, eliminating any
+ indentation. [Grrr... Looking at you VS!]
a) Sadly, there is no avoiding this. Addition of any sort of "markup" to control that
will interfere with the readability AND the final docs rendering.
4) Always use a different numbering style for sub lists/items
@@ -147,6 +147,6 @@ render properly in final docs.
API signaure and parameter info. Different editors may allow control of that.
i) In VS [2019|2022] for C# it is controlled by
`Text Editor > C# > Advanced > Editor Help: "Show remarks in Quick Info."`
- ii) Turning this off can greatly reduce the noise AND reduce the problems of
- different rende
+ 1) Turning this off can greatly reduce the noise AND reduce the problems of
+ different rendering as lists are generally not used in the other elements.
diff --git a/src/Interop/Ubiquity.NET.Llvm.Interop/Library.cs b/src/Interop/Ubiquity.NET.Llvm.Interop/Library.cs
index b8909b3da..2a54479b6 100644
--- a/src/Interop/Ubiquity.NET.Llvm.Interop/Library.cs
+++ b/src/Interop/Ubiquity.NET.Llvm.Interop/Library.cs
@@ -49,6 +49,12 @@ public static ILibLlvm InitializeLLVM( )
}
// Verify the version of LibLLVM.
+ string verString = LibLLVMGetVersion()?.ToString() ?? string.Empty;
+ if(string.IsNullOrWhiteSpace(verString))
+ {
+ throw new InvalidOperationException("Internal error: LLVM reported an empty string for version!");
+ }
+
var libVersion = SemVer.Parse(LibLLVMGetVersion()?.ToString() ?? string.Empty, SemVerFormatProvider.CaseInsensitive);
if( libVersion is CSemVerCI semVerCI)
{
diff --git a/src/Samples/CodeGenWithDebugInfo/Program.cs b/src/Samples/CodeGenWithDebugInfo/Program.cs
index 31a8a9ca4..b9caa0289 100644
--- a/src/Samples/CodeGenWithDebugInfo/Program.cs
+++ b/src/Samples/CodeGenWithDebugInfo/Program.cs
@@ -113,13 +113,35 @@ public static void Main( string[] args )
, constArray
);
- var bar = module.AddGlobal( fooType, false, 0, barValue, "bar" );
+ var bar = module.AddGlobal( fooType, false, 0, barValue, "bar"u8 );
bar.Alignment = module.Layout.AbiAlignmentOf( fooType );
- bar.AddDebugInfo( diBuilder.CreateGlobalVariableExpression( compilationUnit, "bar", string.Empty, diFile, 8, fooType.DebugInfoType, false, null ) );
-
- var baz = module.AddGlobal( fooType, false, Linkage.Common, Constant.NullValueFor( fooType ), "baz" );
+ bar.AddDebugInfo(
+ diBuilder.CreateGlobalVariableExpression(
+ compilationUnit,
+ "bar"u8,
+ linkageName: string.Empty,
+ diFile,
+ lineNo: 8,
+ fooType.DebugInfoType,
+ isLocalToUnit: false,
+ value: null
+ )
+ );
+
+ var baz = module.AddGlobal( fooType, false, Linkage.Common, Constant.NullValueFor( fooType ), "baz"u8 );
baz.Alignment = module.Layout.AbiAlignmentOf( fooType );
- baz.AddDebugInfo( diBuilder.CreateGlobalVariableExpression( compilationUnit, "baz", string.Empty, diFile, 9, fooType.DebugInfoType, false, null ) );
+ baz.AddDebugInfo(
+ diBuilder.CreateGlobalVariableExpression(
+ compilationUnit,
+ "baz"u8,
+ linkageName: string.Empty,
+ diFile,
+ lineNo: 9,
+ fooType.DebugInfoType,
+ isLocalToUnit: false,
+ value: null
+ )
+ );
// add module flags and compiler identifiers...
// this can technically occur at any point, though placing it here makes
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/AnalyzerConfigOptionsProviderExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/AnalyzerConfigOptionsProviderExtensions.cs
new file mode 100644
index 000000000..0fc338d3d
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/AnalyzerConfigOptionsProviderExtensions.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+// Mostly from https://github.com/Sergio0694/PolySharp/blob/main/src/PolySharp.SourceGenerators/Extensions/AnalyzerConfigOptionsProviderExtensions.cs
+// Reformatted and made to conform to repo guides
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Extension methods for the type.
+ public static class AnalyzerConfigOptionsProviderExtensions
+ {
+ /// Checks whether the input property has a valid value.
+ /// The input instance.
+ /// The Build property name.
+ /// The resulting property value, if invalid.
+ /// Whether the target property is a valid value.
+ public static bool IsValidBoolBuildProperty(
+ this AnalyzerConfigOptionsProvider options,
+ string propertyName,
+ [NotNullWhen( false )] out string? propertyValue
+ )
+ {
+ return !options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out propertyValue )
+ || string.IsNullOrEmpty( propertyValue )
+ || string.Equals( propertyValue, bool.TrueString, StringComparison.OrdinalIgnoreCase )
+ || string.Equals( propertyValue, bool.FalseString, StringComparison.OrdinalIgnoreCase );
+ }
+
+ /// Gets the value of a build property.
+ /// The input instance.
+ /// The build property name.
+ /// The value of the specified build property.
+ ///
+ /// The return value is equivalent to a (case insensitive) '$(PropertyName)' == 'true' check.
+ /// That is, any other value, including empty/not present, is considered .
+ ///
+ public static bool GetBoolBuildProperty( this AnalyzerConfigOptionsProvider options, string propertyName )
+ {
+ return options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out string? propertyValue )
+ && string.Equals( propertyValue, bool.TrueString, StringComparison.OrdinalIgnoreCase );
+ }
+
+ /// Gets the value of a Build property representing a semicolon-separated list of strings.
+ /// The input instance.
+ /// The build property name.
+ /// The value of the specified build property.
+ public static ImmutableArray GetStringArrayBuildProperty( this AnalyzerConfigOptionsProvider options, string propertyName )
+ {
+ return options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out string? propertyValue )
+ ? [ .. propertyValue.Split( ',', ';' ) ]
+ : [];
+ }
+
+ // MSBuild properties that are visible to the compiler are available with the "build_property." prefix
+ // See: https://andrewlock.net/creating-a-source-generator-part-13-providing-and-accessing-msbuild-settings-in-source-generators/
+ private const string BuildProperty = "build_property";
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/BaseTypeDeclarationSyntaxExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/BaseTypeDeclarationSyntaxExtensions.cs
new file mode 100644
index 000000000..6ca2942b5
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/BaseTypeDeclarationSyntaxExtensions.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Utility type to provide extensions for
+ public static class BaseTypeDeclarationSyntaxExtensions
+ {
+ // The following 2 extension methods are based on:
+ // https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/
+
+ /// Gets the declared namespace for a
+ /// Syntax to get the namespace for
+ /// Namespace of
+ public static string GetDeclaredNamespace(this BaseTypeDeclarationSyntax syntax)
+ {
+ // If we don't have a namespace at all we'll return an empty string
+ // This accounts for the "default namespace" case
+ string nameSpace = string.Empty;
+
+ // Get the containing syntax node for the type declaration
+ // (could be a nested type, for example)
+ SyntaxNode? potentialNamespaceParent = syntax.Parent;
+
+ // Keep moving "out" of nested classes etc until we get to a namespace
+ // or until we run out of parents
+ while (potentialNamespaceParent != null
+ && potentialNamespaceParent is not NamespaceDeclarationSyntax
+ && potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax)
+ {
+ potentialNamespaceParent = potentialNamespaceParent.Parent;
+ }
+
+ // Build up the final namespace by looping until we no longer have a namespace declaration
+ if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent)
+ {
+ // We have a namespace. Use that as the type
+ nameSpace = namespaceParent.Name.ToString();
+
+ // Keep moving "out" of the namespace declarations until there
+ // are no more nested namespace declarations.
+ while (true)
+ {
+ if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent)
+ {
+ break;
+ }
+
+ // Add the outer namespace as a prefix to the final namespace
+ nameSpace = $"{namespaceParent.Name}.{nameSpace}";
+ namespaceParent = parent;
+ }
+ }
+
+ // return the final namespace
+ return nameSpace;
+ }
+
+ /// Gets the nested class name for a
+ /// Syntax to get the name for
+ /// Flag to indicate if the type itself is included in the name [Default:
+ /// of the syntax or
+ public static NestedClassName? GetNestedClassName( this BaseTypeDeclarationSyntax syntax, bool includeSelf = false)
+ {
+ // Try and get the parent syntax. If it isn't a type like class/struct, this will be null
+ TypeDeclarationSyntax? parentSyntax = includeSelf ? syntax as TypeDeclarationSyntax : syntax.Parent as TypeDeclarationSyntax;
+ NestedClassName? parentClassInfo = null;
+
+ // We can only be nested in class/struct/record
+
+ // Keep looping while we're in a supported nested type
+ while (parentSyntax is not null)
+ {
+ // NOTE: due to bug https://github.com/dotnet/roslyn/issues/78042 this
+ // is not using a local static function to evaluate this in the condition
+ // of the while loop [Workaround: go back to "old" extension syntax...]
+ var rawKind = parentSyntax.Kind();
+ bool isAllowedKind
+ = rawKind == SyntaxKind.ClassDeclaration
+ || rawKind == SyntaxKind.StructDeclaration
+ || rawKind == SyntaxKind.RecordDeclaration;
+
+ if (!isAllowedKind)
+ {
+ break;
+ }
+
+ // Record the parent type keyword (class/struct etc), name, and constraints
+ parentClassInfo = new NestedClassName(
+ keyword: parentSyntax.Keyword.ValueText,
+ name: parentSyntax.Identifier.ToString() + parentSyntax.TypeParameterList,
+ constraints: parentSyntax.ConstraintClauses.ToString(),
+ children: parentClassInfo is null ? [] : [parentClassInfo]); // set the child link (null initially)
+
+ // Move to the next outer type
+ parentSyntax = parentSyntax.Parent as TypeDeclarationSyntax;
+ }
+
+ // return a link to the outermost parent type
+ return parentClassInfo;
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/CompilationExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/CompilationExtensions.cs
new file mode 100644
index 000000000..eb631224e
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/CompilationExtensions.cs
@@ -0,0 +1,177 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+// Mostly from: https://github.com/Sergio0694/PolySharp/blob/main/src/PolySharp.SourceGenerators/Extensions/CompilationExtensions.cs
+// Reformated and adapted to support repo guidelines
+
+using Microsoft.CodeAnalysis.VisualBasic;
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Structure for a runtime version
+ /// Name of the runtime
+ /// Version of the runtime
+ [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Simple record" )]
+ public readonly record struct RuntimeVersion( string RuntimeName, Version Version );
+
+ /// Extension methods for the type.
+ public static class CompilationExtensions
+ {
+ /// Checks whether a given compilation (assumed to be for C#) is using at least a given language version.
+ /// The to consider for analysis.
+ /// The minimum language version to check.
+ /// Whether is using at least the specified language version.
+ public static bool HasLanguageVersionAtLeastEqualTo( this Compilation compilation, Microsoft.CodeAnalysis.CSharp.LanguageVersion languageVersion )
+ {
+ return compilation is not CSharpCompilation csharpCompilation
+ ? throw new ArgumentNullException( nameof( compilation ) )
+ : csharpCompilation.LanguageVersion >= languageVersion;
+ }
+
+ /// Checks whether a given VB compilation is using at least a given language version.
+ /// The to consider for analysis.
+ /// The minimum language version to check.
+ /// Whether is using at least the specified language version.
+ [SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "not Hungarian" )]
+ public static bool HasLanguageVersionAtLeastEqualTo( this Compilation compilation, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersion )
+ {
+ return compilation is not VisualBasicCompilation vbCompilation
+ ? throw new ArgumentNullException( nameof( compilation ) )
+ : vbCompilation.LanguageVersion >= languageVersion;
+ }
+
+ /// Gets the runtime version by extracting the version from the assembly implementing
+ /// Compilation to get the version information from
+ /// Version of the runtime the compilation is targetting
+ public static RuntimeVersion GetRuntimeVersion(this Compilation self)
+ {
+ var objectType = self.GetSpecialType(SpecialType.System_Object);
+ var runtimeAssembly = objectType.ContainingAssembly;
+ return new(runtimeAssembly.Identity.Name, runtimeAssembly.Identity.Version);
+ }
+
+ /// Gets a value indicating wheter the compilation has a minium version of the runtime
+ /// Compilation to test
+ /// Minimum version accepted
+ /// if the runtime version targetted by the compilation is at least ; otherwise
+ public static bool HasRuntimeVersionAtLeast(this Compilation self, RuntimeVersion minVersion)
+ {
+ var runtimeVersion = GetRuntimeVersion(self);
+ return runtimeVersion.RuntimeName == minVersion.RuntimeName && runtimeVersion.Version >= minVersion.Version;
+ }
+
+ /// Checks whether or not a type with a specified metadata name is accessible from a given instance.
+ /// The to consider for analysis.
+ /// The fully-qualified metadata type name to find.
+ /// Whether a type with the specified metadata name can be accessed from the given compilation.
+ ///
+ /// This method enumerates candidate type symbols to find a match in the following order:
+ /// 1) If only one type with the given name is found within the compilation and its referenced assemblies, check its accessibility.
+ /// 2) If the current defines the symbol, check its accessibility.
+ /// 3) Otherwise, check whether the type exists and is accessible from any of the referenced assemblies.
+ ///
+ public static bool HasAccessibleTypeWithMetadataName( this Compilation compilation, string fullyQualifiedMetadataName )
+ {
+ if(compilation is null)
+ {
+ throw new ArgumentNullException( nameof( compilation ) );
+ }
+
+ if(string.IsNullOrWhiteSpace( fullyQualifiedMetadataName ))
+ {
+ throw new ArgumentException( $"'{nameof( fullyQualifiedMetadataName )}' cannot be null or whitespace.", nameof( fullyQualifiedMetadataName ) );
+ }
+
+ // If there is only a single matching symbol, check its accessibility
+ if(compilation.GetTypeByMetadataName( fullyQualifiedMetadataName ) is INamedTypeSymbol typeSymbol)
+ {
+ return compilation.IsSymbolAccessibleWithin( typeSymbol, compilation.Assembly );
+ }
+
+ // Otherwise, check all available types
+ foreach(INamedTypeSymbol currentTypeSymbol in compilation.GetTypesByMetadataName( fullyQualifiedMetadataName ))
+ {
+ if(compilation.IsSymbolAccessibleWithin( currentTypeSymbol, compilation.Assembly ))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// Checks whether or not a type with a specified metadata name is accessible from a given instance.
+ /// The to consider for analysis.
+ /// The fully-qualified metadata type name to find.
+ /// Name of the member
+ /// Whether a type with the specified metadata name can be accessed from the given compilation.
+ ///
+ /// This method enumerates candidate type symbols to find a match in the following order:
+ /// 1) If only one type with the given name is found within the compilation and its referenced assemblies, check its accessibility.
+ /// 2) If the current defines the symbol, check its accessibility.
+ /// 3) Otherwise, check whether the type exists and is accessible from any of the referenced assemblies.
+ ///
+ public static bool HasAccessibleMember( this Compilation compilation, string fullyQualifiedMetadataName, string memberName )
+ {
+ // If there is only a single matching symbol, check its accessibility
+ if(compilation.GetTypeByMetadataName( fullyQualifiedMetadataName ) is INamedTypeSymbol typeSymbol)
+ {
+ return compilation.IsSymbolAccessibleWithin( typeSymbol, compilation.Assembly )
+ && compilation.HasAccessibleMemberWithin( typeSymbol, memberName, compilation.Assembly);
+ }
+
+ // Otherwise, check all available types
+ foreach(INamedTypeSymbol currentTypeSymbol in compilation.GetTypesByMetadataName( fullyQualifiedMetadataName ))
+ {
+ if(compilation.IsSymbolAccessibleWithin( currentTypeSymbol, compilation.Assembly )
+ && compilation.HasAccessibleMemberWithin( currentTypeSymbol, memberName, compilation.Assembly)
+ )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// Tests if a has a type with an accessible member of a given name
+ /// to test
+ /// Type symbol for the type to test
+ /// Name of the member to test for
+ /// Symbol to test if the member is accessible within
+ /// Symbol to use for "protected access" [default: null]
+ /// if the member is accesible and
+ public static bool HasAccessibleMemberWithin(
+ this Compilation self,
+ ITypeSymbol typeSymbol,
+ string memberName,
+ ISymbol within,
+ ITypeSymbol? throughType = null
+ )
+ {
+ if(self is null)
+ {
+ throw new ArgumentNullException( nameof( self ) );
+ }
+
+ if(typeSymbol is null)
+ {
+ throw new ArgumentNullException( nameof( typeSymbol ) );
+ }
+
+ if(string.IsNullOrEmpty( memberName ))
+ {
+ throw new ArgumentException( $"'{nameof( memberName )}' cannot be null or empty.", nameof( memberName ) );
+ }
+
+ if(within is null)
+ {
+ throw new ArgumentNullException( nameof( within ) );
+ }
+
+ var memberSymbol = typeSymbol.GetMembers().Where(s=>s.Name == memberName).FirstOrDefault();
+ return memberSymbol is not null
+ && self.IsSymbolAccessibleWithin(memberSymbol, within, throughType);
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/DiagnosticInfo.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/DiagnosticInfo.cs
new file mode 100644
index 000000000..fe67ebd7a
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/DiagnosticInfo.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+// Modified from idea in blog post: https://andrewlock.net/creating-a-source-generator-part-9-avoiding-performance-pitfalls-in-incremental-generators/
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Contains diagnostic information collected for reporting to the host
+ ///
+ /// This is an equatable type and therefore is legit for use in generators/analyzers where
+ /// that is needed for caching. A is not, so this record bundles
+ /// the parameters needed for creation of one and defers the construction until needed.
+ ///
+ public sealed record DiagnosticInfo
+ {
+ /// Initializes a new instance of the class.
+ /// Descriptor for the diagnostic
+ /// Location in the source file that triggered this diagnostic
+ /// Args for the message
+ public DiagnosticInfo(DiagnosticDescriptor descriptor, Location? location, params IEnumerable msgArgs)
+ {
+ Descriptor = descriptor;
+ Location = location;
+ Params = msgArgs.ToImmutableArray();
+ }
+
+ /// Gets the parameters for this diagnostic
+ public EquatableArray Params { get; }
+
+ /// Gets the descriptor for this diagnostic
+ public DiagnosticDescriptor Descriptor { get; }
+
+ // Location is an abstract type but all derived types implement IEquatable where T is Location
+ // Thus a location is equatable even though the base abstract type doesn't implement that interface.
+
+ /// Gets the location of the source of this diagnostic
+ public Location? Location { get; }
+
+ /// Factory to create a from the information contained in this holder
+ /// that represents this information
+ public Diagnostic CreateDiagnostic()
+ {
+ return Diagnostic.Create(Descriptor, Location, Params.ToArray());
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableArray.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableArray.cs
new file mode 100644
index 000000000..29a5c8e3f
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableArray.cs
@@ -0,0 +1,209 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+#pragma warning disable SA1642 // Constructor summary documentation should begin with standard text
+#pragma warning disable SA1615 // Element return value should be documented
+#pragma warning disable SA1604 // Element documentation should have summary
+#pragma warning disable SA1611 // Element parameters should be documented
+#pragma warning disable CA1000 // Do not declare static members on generic types
+#pragma warning disable CA2225 // Operator overloads have named alternates
+
+// ORIGINALLY FROM: https://github.com/CommunityToolkit/dotnet/blob/7b53ae23dfc6a7fb12d0fc058b89b6e948f48448/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray%7BT%7D.cs
+
+// 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 Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Extensions for .
+ public static class EquatableArray
+ {
+ /// Creates an instance from a given .
+ /// The type of items in the input array.
+ /// The input instance.
+ /// An instance from a given .
+ public static EquatableArray AsEquatableArray(this ImmutableArray array)
+ where T : IEquatable
+ {
+ return array.IsDefault ? throw new ArgumentNullException(nameof(array))
+ : new(array);
+ }
+ }
+
+ ///
+ /// An immutable, equatable array. This is equivalent to but with value equality of members support.
+ ///
+ /// The type of values in the array.
+ public readonly struct EquatableArray
+ : IEquatable>
+ , IEnumerable
+ where T : IEquatable
+ {
+ ///
+ /// The underlying array.
+ ///
+ private readonly T[]? array;
+
+ ///
+ /// Creates a new instance.
+ ///
+ /// The input to wrap.
+ public EquatableArray(ImmutableArray array)
+ {
+ this.array = Unsafe.As, T[]?>(ref array);
+ }
+
+ ///
+ /// Gets a reference to an item at a specified position within the array.
+ ///
+ /// The index of the item to retrieve a reference to.
+ /// A reference to an item at a specified position within the array.
+ public ref readonly T this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ref AsImmutableArray().ItemRef(index);
+ }
+
+ ///
+ /// Gets a value indicating whether the current array is empty.
+ ///
+ public bool IsEmpty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => AsImmutableArray().IsEmpty;
+ }
+
+ /// Gets the length of the array
+ public int Length => array?.Length ?? 0;
+
+ ///
+ public bool Equals(EquatableArray array)
+ {
+ return AsSpan().SequenceEqual(array.AsSpan());
+ }
+
+ ///
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ return obj is EquatableArray array && Equals(this, array);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ if (this.array is not T[] array)
+ {
+ return 0;
+ }
+
+ HashCode hashCode = default;
+
+ foreach (T item in array)
+ {
+ hashCode.Add(item);
+ }
+
+ return hashCode.ToHashCode();
+ }
+
+ ///
+ /// Gets an instance from the current .
+ ///
+ /// The from the current .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ImmutableArray AsImmutableArray()
+ {
+ return Unsafe.As>(ref Unsafe.AsRef(in array));
+ }
+
+ ///
+ /// Creates an instance from a given .
+ ///
+ /// The input instance.
+ /// An instance from a given .
+ public static EquatableArray FromImmutableArray(ImmutableArray array)
+ {
+ return new(array);
+ }
+
+ ///
+ /// Returns a wrapping the current items.
+ ///
+ /// A wrapping the current items.
+ public ReadOnlySpan AsSpan()
+ {
+ return AsImmutableArray().AsSpan();
+ }
+
+ ///
+ /// Copies the contents of this instance to a mutable array.
+ ///
+ /// The newly instantiated array.
+ public T[] ToArray()
+ {
+ return [.. AsImmutableArray()];
+ }
+
+ ///
+ /// Gets an value to traverse items in the current array.
+ ///
+ /// An value to traverse items in the current array.
+ public ImmutableArray.Enumerator GetEnumerator()
+ {
+ return AsImmutableArray().GetEnumerator();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)AsImmutableArray()).GetEnumerator();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)AsImmutableArray()).GetEnumerator();
+ }
+
+ ///
+ /// Implicitly converts an to .
+ ///
+ /// An instance from a given .
+ public static implicit operator EquatableArray(ImmutableArray array)
+ {
+ return FromImmutableArray(array);
+ }
+
+ ///
+ /// Implicitly converts an to .
+ ///
+ /// An instance from a given .
+ public static implicit operator ImmutableArray(EquatableArray array)
+ {
+ return array.AsImmutableArray();
+ }
+
+ ///
+ /// Checks whether two values are the same.
+ ///
+ /// The first value.
+ /// The second value.
+ /// Whether and are equal.
+ public static bool operator ==(EquatableArray left, EquatableArray right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Checks whether two values are not the same.
+ ///
+ /// The first value.
+ /// The second value.
+ /// Whether and are not equal.
+ public static bool operator !=(EquatableArray left, EquatableArray right)
+ {
+ return !left.Equals(right);
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalNamespaceImports.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalNamespaceImports.cs
new file mode 100644
index 000000000..ffd720fe7
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalNamespaceImports.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+/*
+NOTE:
+While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT.
+The build property will auto include an invisible and undiscoverable (without looking up obscure documentation)
+set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature.
+By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of
+that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer,
+where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/.
+For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6
+*/
+
+// BUG: False positive from IDE0005 - Using directive is unnecessary
+// Attempts to remove/sort are at least able to figure it out and do the right thing.
+// Bug seems to be related to multi-targetting.
+#pragma warning disable IDE0005
+
+global using System;
+global using System.CodeDom.Compiler;
+global using System.Collections;
+global using System.Collections.Generic;
+global using System.Collections.Immutable;
+global using System.Diagnostics.CodeAnalysis;
+global using System.IO;
+global using System.Linq;
+global using System.Reflection;
+global using System.Runtime.CompilerServices;
+global using System.Text;
+
+global using Microsoft.CodeAnalysis;
+global using Microsoft.CodeAnalysis.CSharp;
+global using Microsoft.CodeAnalysis.CSharp.Syntax;
+global using Microsoft.CodeAnalysis.Diagnostics;
+global using Microsoft.CodeAnalysis.Text;
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalSuppressions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalSuppressions.cs
new file mode 100644
index 000000000..2b0ab7675
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalSuppressions.cs
@@ -0,0 +1,7 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/MemberDeclarationSyntaxExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/MemberDeclarationSyntaxExtensions.cs
new file mode 100644
index 000000000..2177bf75c
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/MemberDeclarationSyntaxExtensions.cs
@@ -0,0 +1,82 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Utility class to provide extensions for
+ public static class MemberDeclarationSyntaxExtensions
+ {
+ /// Determines if a contains a specified attribute or not
+ /// to test
+ /// name of the attribute
+ /// if the attribute is found or if not
+ /// is
+ public static bool HasAttribute(this MemberDeclarationSyntax self, string attributeName)
+ {
+ return self is not null
+ ? self.TryGetAttribute(attributeName, out _)
+ : throw new ArgumentNullException(nameof(self));
+ }
+
+ /// Tries to get an from a
+ /// The to get the attribute from
+ /// name of the attribute
+ /// resulting or if not found
+ /// if the attribute is found or if not
+ public static bool TryGetAttribute(
+ this MemberDeclarationSyntax self,
+ string attributeName,
+ [NotNullWhen(true)] out AttributeSyntax? value
+ )
+ {
+ value = null;
+ string shortName = attributeName;
+ if (attributeName.EndsWith("Attribute"))
+ {
+ shortName = shortName[..^9];
+ }
+ else
+ {
+ attributeName += "Attribute";
+ }
+
+ var q = from attributeList in self.AttributeLists
+ from attribute in attributeList.Attributes
+ let name = attribute.GetIdentifierName()
+ where name == attributeName || name == shortName
+ select attribute;
+
+ value = q.FirstOrDefault();
+ return value != null;
+ }
+
+ /// Determines if a declartion has an extern modifier
+ /// to test
+ /// if has the modifier or not
+ /// is null
+ public static bool IsExtern(this MemberDeclarationSyntax self)
+ {
+ return self is not null
+ ? self.Modifiers.HasExtern()
+ : throw new ArgumentNullException(nameof(self));
+ }
+
+ /// Determines if a declartion has a partial modifier
+ ///
+ public static bool IsPartial(this MemberDeclarationSyntax self)
+ {
+ return self is not null
+ ? self.Modifiers.HasPartialKeyword()
+ : throw new ArgumentNullException(nameof(self));
+ }
+
+ /// Determines if a declartion has a static modifier
+ ///
+ public static bool IsStatic(this MemberDeclarationSyntax self)
+ {
+ return self is not null
+ ? self.Modifiers.HasStatic()
+ : throw new ArgumentNullException(nameof(self));
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/MethodDeclarationSyntaxExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/MethodDeclarationSyntaxExtensions.cs
new file mode 100644
index 000000000..2f4198212
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/MethodDeclarationSyntaxExtensions.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Utility class to provide extensions for
+ public static class MethodDeclarationSyntaxExtensions
+ {
+ // LibraryImportAttribute - intentionally NOT reported as a P/Invoke
+ // It uses different qualifiers and, technically, is NOT a P/Invoke
+ // signature (It's a generated marshaling function with a nested private
+ // P/Invoke using NO marshaling)
+
+ /// Determines if a method declaration is a P/Invoke
+ /// The to test
+ /// if is a P/Invoke declaration or if not
+ ///
+ /// LibraryImportAttribute is intentionally NOT reported as a P/Invoke. It uses different qualifiers and,
+ /// technically, is NOT a P/Invoke signature (It's a marker for a Roslyn source generator. The generated function
+ /// contains the marshaling with a nested private P/Invoke using NO marshaling)
+ ///
+ public static bool IsPInvoke(this MethodDeclarationSyntax self)
+ {
+ return self.IsStatic()
+ && self.IsExtern()
+ && self.HasAttribute("System.Runtime.InteropServices.DllImportAttribute");
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/NestedClassName.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/NestedClassName.cs
new file mode 100644
index 000000000..7aea1b6ef
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/NestedClassName.cs
@@ -0,0 +1,86 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+// Originally FROM: https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/
+// Modified to support IEquatable for caching
+// Additional functionality as needed to generalize it.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Cacheable storage of nested class names for use in source generation
+ public sealed class NestedClassName
+ : IEquatable
+ {
+ /// Initializes a new instance of the class.
+ /// Keyword for this declaration
+ /// Name of the type
+ /// Constraints for this type
+ /// Names of any nested child types to form hiearachies
+ ///
+ /// is normally one of ("class", "struct", "interface", "record [class|struct]?").
+ ///
+ public NestedClassName(string keyword, string name, string constraints, params IEnumerable children)
+ {
+ Keyword = keyword;
+ Name = name;
+ Constraints = constraints;
+ Children = children.ToImmutableArray().AsEquatableArray();
+ }
+
+ /// Gets child nested types
+ public EquatableArray Children { get; }
+
+ /// Gets the keyword for this type
+ ///
+ /// This is normally one of ("class", "struct", "interface", "record [class|struct]?"
+ ///
+ public string Keyword { get; }
+
+ /// Gets the name of the nested type
+ public string Name { get; }
+
+ /// Gets the constraints for a nested type
+ public string Constraints { get; }
+
+ /// Gets a value indicating whether this name contains constraints
+ public bool HasConstraints => !string.IsNullOrWhiteSpace(Constraints);
+
+ /// Compares this instance with another
+ /// Value to compare this instance with
+ /// if the is equal to this instance
+ ///
+ /// This is, at worst, a recursive O(n) operation! However, since it is used for nested types
+ /// the actual depth is statistically rather small and nearly always 0 (Children is empty).
+ /// Deeply nested type declarations is a VERY rare anti-pattern so not a real world problem.
+ ///
+ public bool Equals(NestedClassName other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ // NOTE: This is a recursive O(n) operation!
+ return Equals(Children, other.Children)
+ && Name.Equals( other.Name, StringComparison.Ordinal )
+ && Constraints.Equals( other.Constraints, StringComparison.Ordinal );
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is NestedClassName parentClass && Equals(parentClass);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Children, Keyword, Name, Constraints);
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/Result.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/Result.cs
new file mode 100644
index 000000000..68af2179f
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/Result.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Container for a result or an error descriptor ()
+ /// Value contained in the result [Constrained to ]
+ ///
+ ///
+ /// It is debatable if an incremental generator should produce diagnostics. The official
+ /// cookbook
+ /// recommends against it [Section: "Issue diagnostics"]. Instead it recommends use of an analyzer. While it
+ /// doesn't say what a generator is supposed to do in the event of an input error explicitly, it is implied
+ /// that it should silently ignore them.
+ ///
+ /// This type allows generating diagnostics in the final source production stage of the pipeline by plumbing
+ /// through all of the available data AND diagnostics via this generic record. This acts as a sort of
+ /// discriminated union of results or diagnostics (or possibly a combination of both). All while maintaining
+ /// support for caching with .
+ ///
+ ///
+ public readonly record struct Result
+ where T : IEquatable
+ {
+ /// Initializes a new instance of the struct from a value [No diagnostics]
+ /// Value of the result
+ public Result(T value)
+ : this(value, [])
+ {
+ }
+
+ /// Initializes a new instance of the struct from diagnostics
+ /// Information describing the diagnostics for this result
+ public Result(params IEnumerable diagnostics)
+ : this(default, [.. diagnostics])
+ {
+ }
+
+ /// Initializes a new instance of the struct from a nullable value and set of potentially empty diagnostics
+ /// Value of the result (may be null to indicate no results)
+ /// Array of to describe any diagnostics/warnings encountered while producing
+ ///
+ /// This is the most generalized from of constructor. It supports BOTH a value and diagnostics as it is possible that
+ /// a value is producible, but there are warnings or other informative diagnostics to include with it. Attempts to construct
+ /// a result with no value and no diagnostics throws an exception.
+ ///
+ /// Both and are or empty
+ public Result(T? value, ImmutableArray diagnostics)
+ {
+ if (value is null && diagnostics.IsDefaultOrEmpty)
+ {
+ throw new ArgumentException($"Either {nameof(Value)} or {nameof(diagnostics)} must contain a value");
+ }
+
+ Value = value;
+ Diagnostics = diagnostics;
+ }
+
+ /// Gets the value produced for this result or if no value produced
+ public T? Value { get; init; } = default;
+
+ /// Gets a value indicating whether a value was produced for this result
+ public bool HasValue => Value is not null;
+
+ /// Gets the diagnostics produced for this result (if any)
+ /// This may provide an empty array but is never
+ public EquatableArray Diagnostics { get; init; } = ImmutableArray.Empty;
+
+ /// Gets a value indicating whether this result contains any diagnostics
+ /// This is a shorthand for testing the length of the property
+ public bool HasDiagnostics => !Diagnostics.IsEmpty;
+
+ /// Report all diagnostics to the provided
+ /// to report the diagnostics to
+ ///
+ /// This supports the deferral of reporting with a collection of cahceable . This allows
+ /// for a generatr to report critical internal problems.
+ ///
+ public void ReportDiagnostics(SourceProductionContext ctx)
+ {
+ foreach (var di in Diagnostics)
+ {
+ ctx.ReportDiagnostic(di.CreateDiagnostic());
+ }
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/RoslynExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/RoslynExtensions.cs
new file mode 100644
index 000000000..ba45e641a
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/RoslynExtensions.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Utility class for general Roslyn extensions
+ ///
+ /// This is a place-holder for extensions that don't fit anywhere else and don't really warrant their own type/file.
+ ///
+ public static class RoslynExtensions
+ {
+ /// Gets an identifier name or () if the is not
+ /// to get the identifier from
+ /// Identifier name
+ /// is
+ [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Nested conditionals are NOT simpler" )]
+ public static string GetIdentifierName(this AttributeSyntax self)
+ {
+ if(self is null)
+ {
+ throw new ArgumentNullException(nameof(self));
+ }
+
+ return self.Name is not IdentifierNameSyntax identifier
+ ? string.Empty
+ : identifier.Identifier.ValueText;
+ }
+
+ /// Adds a source file from a manifest resource
+ /// The to add the source to
+ /// Assembly hosting the resource
+ /// Name of the resource
+ /// Hint name for the generated file
+ /// Encoding for the source [Default: ]
+ public static void AddSourceFromResource(
+ this IncrementalGeneratorPostInitializationContext self,
+ Assembly resourceAssembly,
+ string resourceName,
+ string hintName,
+ Encoding? encoding = null
+ )
+ {
+ encoding ??= Encoding.UTF8;
+ using var reader = new StreamReader(resourceAssembly.GetManifestResourceStream(resourceName), encoding);
+ self.AddSource(hintName, SourceText.From(reader, checked((int)reader.BaseStream.Length), encoding));
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/SourceProductionContextExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/SourceProductionContextExtensions.cs
new file mode 100644
index 000000000..d48f94280
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/SourceProductionContextExtensions.cs
@@ -0,0 +1,88 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Utility class to provide extensions for a
+ public static class SourceProductionContextExtensions
+ {
+ /// Reports deferred diagnostics for a
+ /// Type of the result values
+ /// The context to report any diagnostics to
+ /// The result
+ ///
+ /// if has no diagnostics associated with it this is a NOP.
+ ///
+ public static void ReportDiagnostic(this SourceProductionContext self, Result result)
+ where T : IEquatable
+ {
+ if(result.HasDiagnostics)
+ {
+ for(int i = 0; i < result.Diagnostics.Length; ++i)
+ {
+ self.ReportDiagnostic(result.Diagnostics[i]);
+ }
+ }
+ }
+
+ /// Reports a diagnostic to
+ /// The context to report the diagnostic to
+ /// Cached info to report
+ public static void ReportDiagnostic( this SourceProductionContext self, DiagnosticInfo info)
+ {
+ self.ReportDiagnostic(info.CreateDiagnostic());
+ }
+
+ /// Reports a diagnostic to
+ /// The context to report the diagnostic to
+ /// Descriptor of the diagnostic
+ /// Message arguments
+ public static void ReportDiagnostic( this SourceProductionContext self, DiagnosticDescriptor descriptor, params object[] messageArgs)
+ {
+ self.ReportDiagnostic(Diagnostic.Create(descriptor, null, messageArgs));
+ }
+
+ /// Reports a diagnostic to
+ /// The context to report the diagnostic to
+ /// Location of the source of this diagnostic
+ /// Descriptor for the diagnostic
+ /// Argumnets, if any, for the diagnostic message
+ public static void ReportDiagnostic(
+ this SourceProductionContext self,
+ Location location,
+ DiagnosticDescriptor descriptor,
+ params object[] messageArgs
+ )
+ {
+ self.ReportDiagnostic(Diagnostic.Create(descriptor, location, messageArgs));
+ }
+
+ /// Reports a diagnostic to
+ /// The context to report the diagnostic to
+ /// Node as the source of the diagnostic
+ /// Descriptor for the diagnostic
+ /// Argumnets, if any, for the diagnostic message
+ public static void ReportDiagnostic(
+ this SourceProductionContext self,
+ CSharpSyntaxNode node,
+ DiagnosticDescriptor descriptor,
+ params object[] messageArgs
+ )
+ {
+ self.ReportDiagnostic(node.GetLocation(), descriptor, messageArgs);
+ }
+
+ /// Report diagnostics for results to
+ /// Type of the result
+ /// The context to report the diagnostic to
+ /// Array of for the results
+ public static void ReportDiagnostics( this SourceProductionContext self, ImmutableArray> results)
+ where T : IEquatable
+ {
+ for(int i = 0; i < results.Length; ++i)
+ {
+ self.ReportDiagnostic(results[i]);
+ }
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/StringBuilderText.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/StringBuilderText.cs
new file mode 100644
index 000000000..628ea851a
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/StringBuilderText.cs
@@ -0,0 +1,82 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// A implementation over a
+ ///
+ /// This provides a implementation over a
+ /// it provides access to the underlying builder to allow construction of an
+ /// to manage indentation in the output. Manual generation of the output with indentation tracking is the
+ /// recommended approach to generating source output.
+ ///
+ ///
+ public class StringBuilderText
+ : SourceText
+ {
+ /// Initializes a new instance of the class
+ /// Builder to use for building strings
+ /// Encoding to use for the strings
+ /// Hash algorithm to use for debug symbols in the source
+ public StringBuilderText(StringBuilder builder, Encoding encoding, SourceHashAlgorithm algorithm = SourceHashAlgorithm.Sha1)
+ : base(checksumAlgorithm: algorithm)
+ {
+ Builder = builder;
+ InternalEncoding = encoding;
+ }
+
+ /// Initializes a new instance of the class
+ /// Encoding to use for the strings
+ /// Hash algorithm to use for debug symbols in the source
+ ///
+ /// This constructor overload will create a new as the underlying
+ /// store for the strings. This is likely the most common case.
+ ///
+ public StringBuilderText(Encoding encoding, SourceHashAlgorithm algorithm = SourceHashAlgorithm.Sha1)
+ : this(new StringBuilder(), encoding, algorithm)
+ {
+ }
+
+ /// Creates a new over the internal
+ /// The newly created
+ ///
+ /// The created, does NOT dispose of or invalidate the underlying
+ /// . This allows things like
+ /// to work even after is called.
+ /// The created writer is commonly wrapped in an instance of
+ /// for generating source output in a source generator.
+ ///
+ public StringWriter CreateWriter()
+ {
+ return new StringWriter(Builder);
+ }
+
+ /// Gets the internal builder
+ public StringBuilder Builder { get; init; }
+
+ ///
+ public override char this[int position] => Builder[position];
+
+ ///
+ public override Encoding Encoding => InternalEncoding;
+
+ ///
+ public override int Length => Builder.Length;
+
+ ///
+ public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
+ {
+ Builder.CopyTo(sourceIndex, destination, destinationIndex, count);
+ }
+
+ /// Converts the specified span in the underlying builder into a string
+ /// Span to convert
+ /// Text from the builder as a string
+ public override string ToString(TextSpan span)
+ {
+ return Builder.ToString(span.Start, span.Length);
+ }
+
+ private readonly Encoding InternalEncoding;
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/SyntaxTokenListExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/SyntaxTokenListExtensions.cs
new file mode 100644
index 000000000..bf0287dd9
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/SyntaxTokenListExtensions.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Utility class for extensions to
+ public static class SyntaxTokenListExtensions
+ {
+ /// Gets a value indicating whether has the "extern" keyword
+ /// to test
+ /// if the keyword is found if not
+ public static bool HasExtern(this SyntaxTokenList self)
+ {
+ return self.Any(SyntaxKind.ExternKeyword);
+ }
+
+ /// Gets a value indicating whether has the "partial" keyword
+ ///
+ public static bool HasPartialKeyword(this SyntaxTokenList self)
+ {
+ return self.Any(SyntaxKind.PartialKeyword);
+ }
+
+ /// Gets a value indicating whether has the "static" keyword
+ ///
+ public static bool HasStatic(this SyntaxTokenList self)
+ {
+ return self.Any(SyntaxKind.StaticKeyword);
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/TypeSyntaxExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/TypeSyntaxExtensions.cs
new file mode 100644
index 000000000..4088b132c
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/TypeSyntaxExtensions.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.CodeAnalysis.Utils
+{
+ /// Utility class to provide extensions for
+ public static class TypeSyntaxExtensions
+ {
+ /// Tests if is a string
+ /// to test
+ /// if is a string and if not
+ public static bool IsString(this TypeSyntax? self)
+ {
+ return (self is PredefinedTypeSyntax pts && pts.Keyword.IsKind(SyntaxKind.StringKeyword))
+ || (self is QualifiedNameSyntax qns && qns.Left.ToString() == "System" && qns.Right.ToString() == "String");
+ }
+
+ /// Tests if is a void
+ /// to test
+ /// if is a void and if not
+ public static bool IsVoid(this TypeSyntax? self)
+ {
+ return self is PredefinedTypeSyntax pts
+ && pts.Keyword.IsKind(SyntaxKind.VoidKeyword);
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj b/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj
new file mode 100644
index 000000000..0d7dabb50
--- /dev/null
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj
@@ -0,0 +1,30 @@
+
+
+
+ netstandard2.0
+ enable
+
+
+ preview
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj b/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj
index 4013ddc85..d3d73d298 100644
--- a/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj
+++ b/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj
@@ -14,7 +14,18 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
diff --git a/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs b/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs
index a6ba44c0f..552ed2092 100644
--- a/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs
+++ b/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs
@@ -7,12 +7,12 @@ namespace Ubiquity.NET.Extensions
[SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "Extension" )]
public static class AssemblyExtensions
{
- // VS2026 builds of this are OK, however command line/PR/CI builds will generate an error
- // No idea why there's a difference the $(NETCoreSdkVersion) is the same in both so it's
- // unclear why the two things behave differently. This is just another example of why the
- // `extension` keyword is "not yet ready for prime time". Too many things don't support it
- // properly yet. [Hopefully, that works itself out in short order as it's useless unless
- // fully supported]
+ // VS2026 builds of this are OK, however VS2019, command line/PR/CI builds will generate an error.
+ // The VS builds use the VS provided MSBuild, while the command line uses the .NET Core build.
+ // This is just another example of why the `extension` keyword is "not yet ready for prime time".
+ // Too many things don't support it properly yet so use needs justification as the ONLY option and
+ // HEAVY testing to ensure all the issues are accounted for.
+ // [Hopefully, that works itself out in short order as it's a mostly useless feature unless fully supported]
#if COMPILER_SUPPORTS_CALLER_ATTRIBUES_ON_EXTENSION
extension(Assembly self)
{
diff --git a/src/Ubiquity.NET.Extensions/DisposableAction.cs b/src/Ubiquity.NET.Extensions/DisposableAction.cs
index 8938c5d58..a214fed35 100644
--- a/src/Ubiquity.NET.Extensions/DisposableAction.cs
+++ b/src/Ubiquity.NET.Extensions/DisposableAction.cs
@@ -21,11 +21,9 @@ public DisposableAction( Action onDispose, [CallerArgumentExpression(nameof(onDi
}
/// Runs the action provided in the constructor ()
- /// This instance is already disposed
public void Dispose( )
{
var disposeOp = Interlocked.Exchange(ref OnDispose, null);
- ObjectDisposedException.ThrowIf(disposeOp is null, this);
disposeOp!();
}
diff --git a/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs b/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs
index 21c6bb383..ec3908330 100644
--- a/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs
+++ b/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs
@@ -46,9 +46,9 @@ public static class ExceptionValidationExtensions
[DebuggerStepThrough]
public static T ThrowIfNull( [NotNull] this T? self, [CallerArgumentExpression( nameof( self ) )] string? exp = null )
{
- ArgumentNullException.ThrowIfNull( self, exp );
-
- return self;
+ return self is null
+ ? throw new ArgumentNullException(exp)
+ : self;
}
/// Throws an exception if an argument is outside of a given (Inclusive) range
@@ -59,13 +59,12 @@ public static T ThrowIfNull( [NotNull] this T? self, [CallerArgumentExpressio
/// Name or expression of the value in [Default: provided by compiler]
///
[DebuggerStepThrough]
+ [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Not simpler, more readable this way" )]
public static T ThrowIfOutOfRange( this T self, T min, T max, [CallerArgumentExpression( nameof( self ) )] string? exp = null )
where T : struct, IComparable
{
- ArgumentNullException.ThrowIfNull(self, exp);
- ArgumentOutOfRangeException.ThrowIfLessThan( self, min, exp );
- ArgumentOutOfRangeException.ThrowIfGreaterThan( self, max, exp );
-
+ ArgumentOutOfRangeException.ThrowIfLessThan(self, min, exp);
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(self, max, exp);
return self;
}
@@ -104,9 +103,8 @@ public static T ThrowIfNotDefined( this T self, [CallerArgumentExpression( na
// least includes the original value in question. (Normally an enum does fit an int, but for
// interop might not) the resulting exception will have "ParamName" as the default of "null"!
//
- // TODO: Move the exception message to a resource for globalization
// This matches the overloaded constructor version but allows for reporting enums with non-int underlying type.
- throw new InvalidEnumArgumentException( $"The value of argument '{exp}' ({self}) is invalid for Enum of type '{typeof( T )}'" );
+ throw new InvalidEnumArgumentException( SR.Format( nameof( Resources.InvalidEnumArgument_NonInt ), exp, self, typeof( T ) ) );
}
}
}
diff --git a/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs b/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs
index 2ca3f6006..8137e3280 100644
--- a/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs
+++ b/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs
@@ -26,13 +26,10 @@ set of namespaces that is NOT consistent or controlled by the developer. THAT is
global using System.Diagnostics.CodeAnalysis;
global using System.Globalization;
global using System.IO;
+global using System.Linq;
global using System.Reflection;
global using System.Runtime.CompilerServices;
global using System.Text;
global using System.Threading;
-global using Ubiquity.NET.Extensions;
global using Ubiquity.NET.Extensions.Properties;
-
-// alias allows simpler porting of polyfill from .NET sources
-global using SR = Ubiquity.NET.Extensions.Properties.Resources;
diff --git a/src/Ubiquity.NET.Extensions/PolyFillExceptionValidators.cs b/src/Ubiquity.NET.Extensions/PolyFillExceptionValidators.cs
deleted file mode 100644
index 7f4c3250a..000000000
--- a/src/Ubiquity.NET.Extensions/PolyFillExceptionValidators.cs
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
-// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
-
-// .NET 7 added the various exception static methods for parameter validation
-// This will back fill them for earlier versions.
-//
-// NOTE: C #14 extension keyword support is required to make this work.
-#if !NET7_0_OR_GREATER
-
-#pragma warning disable IDE0130 // Namespace does not match folder structure
-
-namespace System
-{
- /// poly fill extensions for static methods added in .NET 7
- ///
- /// This requires support of the C#14 keyword `extension` to work properly. There is
- /// no other way to add static methods to non-partial types for source compatibility.
- /// Otherwise code cannot use the modern .NET runtime implementations and instead
- /// must always use some extension methods, or litter around a LOT of #if/#else/#endif
- /// based on the framework version...
- ///
- [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "Extension - Broken analyzer" )]
- [SuppressMessage( "Naming", "CA1708:Identifiers should differ by more than case", Justification = "Extension - broken analyzer" )]
- public static class PolyFillExceptionValidators
- {
- /// Poly fill Extensions for
- extension( ArgumentException )
- {
- /// Throw an if a string is m empty, or all whitepsace.
- /// input string to test
- /// expression or name of the string to test; normally provided by compiler
- /// string is m empty, or all whitepsace
- public static void ThrowIfNullOrWhiteSpace(
- [NotNull] string? argument,
- [CallerArgumentExpression( nameof( argument ) )] string? paramName = null
- )
- {
- ArgumentNullException.ThrowIfNull(argument, paramName);
-
- // argument is non-null verified by this, sadly older frameworks don't have
- // attributes to declare that.
- if(string.IsNullOrWhiteSpace( argument ))
- {
- throw new ArgumentException( SR.Argument_EmptyOrWhiteSpaceString, paramName );
- }
- }
- }
-
- /// Poly fill Extensions for
- extension( ArgumentNullException )
- {
- /// Throws an aexception if the tested argument is
- /// value to test
- /// expression for the name of the value; normally provided by compiler
- /// is
- public static void ThrowIfNull(
- [NotNull] object? argument,
- [CallerArgumentExpression( nameof( argument ) )] string? paramName = default
- )
- {
- if(argument is null)
- {
- throw new ArgumentNullException( paramName );
- }
- }
- }
-
- /// Poly fill Extensions for
- extension( ObjectDisposedException )
- {
- /// Throws an if is .
- /// Condition to determine if the instance is disposed
- /// instance that is tested; Used to get type name for exception
- /// is
- public static void ThrowIf(
- [DoesNotReturnIf( true )] bool condition,
- object instance
- )
- {
- if(condition)
- {
- throw new ObjectDisposedException( instance?.GetType().FullName );
- }
- }
- }
-
- /// Poly fill Extensions for
- extension( ArgumentOutOfRangeException )
- {
- /// Throws an if is equal to .
- /// The argument to validate as not equal to .
- /// The value to compare with .
- /// The name of the parameter with which corresponds.
- public static void ThrowIfEqual( T value, T other, [CallerArgumentExpression( nameof( value ) )] string? paramName = null )
- where T : IEquatable?
- {
- if(EqualityComparer.Default.Equals( value, other ))
- {
- ThrowEqual( value, other, paramName );
- }
- }
-
- /// Throws an if is not equal to .
- /// The argument to validate as equal to .
- /// The value to compare with .
- /// The name of the parameter with which corresponds.
- public static void ThrowIfNotEqual( T value, T other, [CallerArgumentExpression( nameof( value ) )] string? paramName = null )
- where T : IEquatable?
- {
- if(!EqualityComparer.Default.Equals( value, other ))
- {
- ThrowNotEqual( value, other, paramName );
- }
- }
-
- /// Throws an if is greater than .
- /// The argument to validate as less or equal than .
- /// The value to compare with .
- /// The name of the parameter with which corresponds.
- public static void ThrowIfGreaterThan( T value, T other, [CallerArgumentExpression( nameof( value ) )] string? paramName = null )
- where T : IComparable
- {
- if(value.CompareTo( other ) > 0)
- {
- ThrowGreater( value, other, paramName );
- }
- }
-
- /// Throws an if is greater than or equal .
- /// The argument to validate as less than .
- /// The value to compare with .
- /// The name of the parameter with which corresponds.
- public static void ThrowIfGreaterThanOrEqual( T value, T other, [CallerArgumentExpression( nameof( value ) )] string? paramName = null )
- where T : IComparable
- {
- if(value.CompareTo( other ) >= 0)
- {
- ThrowGreaterEqual( value, other, paramName );
- }
- }
-
- /// Throws an if is less than .
- /// The argument to validate as greatar than or equal than .
- /// The value to compare with .
- /// The name of the parameter with which corresponds.
- public static void ThrowIfLessThan( T value, T other, [CallerArgumentExpression( nameof( value ) )] string? paramName = null )
- where T : IComparable
- {
- if(value.CompareTo( other ) < 0)
- {
- ThrowLess( value, other, paramName );
- }
- }
-
- /// Throws an if is less than or equal .
- /// The argument to validate as greatar than than .
- /// The value to compare with .
- /// The name of the parameter with which corresponds.
- public static void ThrowIfLessThanOrEqual( T value, T other, [CallerArgumentExpression( nameof( value ) )] string? paramName = null )
- where T : IComparable
- {
- if(value.CompareTo( other ) <= 0)
- {
- ThrowLessEqual( value, other, paramName );
- }
- }
-
- [DoesNotReturn]
- private static void ThrowZero( T value, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeNonZero, paramName, value ) );
-
- [DoesNotReturn]
- private static void ThrowNegative( T value, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeNonNegative, paramName, value ) );
-
- [DoesNotReturn]
- private static void ThrowNegativeOrZero( T value, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero, paramName, value ) );
-
- [DoesNotReturn]
- private static void ThrowGreater( T value, T other, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeLessOrEqual, paramName, value, other ) );
-
- [DoesNotReturn]
- private static void ThrowGreaterEqual( T value, T other, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeLess, paramName, value, other ) );
-
- [DoesNotReturn]
- private static void ThrowLess( T value, T other, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual, paramName, value, other ) );
-
- [DoesNotReturn]
- private static void ThrowLessEqual( T value, T other, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeGreater, paramName, value, other ) );
-
- [DoesNotReturn]
- private static void ThrowEqual( T value, T other, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeNotEqual, paramName, (object?)value ?? "null", (object?)other ?? "null" ) );
-
- [DoesNotReturn]
- private static void ThrowNotEqual( T value, T other, string? paramName ) =>
- throw new ArgumentOutOfRangeException( paramName, value, SR.Format( SR.ArgumentOutOfRange_Generic_MustBeEqual, paramName, (object?)value ?? "null", (object?)other ?? "null" ) );
- }
- }
-}
-#endif
diff --git a/src/Ubiquity.NET.Extensions/PolyFillStringExtensions.cs b/src/Ubiquity.NET.Extensions/PolyFillStringExtensions.cs
deleted file mode 100644
index b7f38dbff..000000000
--- a/src/Ubiquity.NET.Extensions/PolyFillStringExtensions.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
-// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
-
-#if !NET6_0_OR_GREATER
-
-// from .NET sources
-// see: https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs
-
-#pragma warning disable IDE0130 // Namespace does not match folder structure
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-#pragma warning disable SA1600 // Elements should be documented [Duplicate of CS1591]
-
-using System.Text.RegularExpressions;
-
-namespace System.Text
-{
- /// Pollyfill extensions for support not present in older runtimes
- ///
- public static class PolyFillStringExtensions
- {
- /// Replace line endings in the string with environment specific forms
- /// string to change line endings for
- /// string with environment specific line endings
- [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1611:Element parameters should be documented", Justification = "Extension" )]
- public static string ReplaceLineEndings(this string self) => ReplaceLineEndings(self, Environment.NewLine);
-
- // This is NOT the most performant implementation, it's going for simplistic pollyfill that has
- // the correct behavior, even if not the most performant. If performance is critical, use a
- // later version of the runtime!
-
- /// Replace line endings in the string with a given string
- /// string to change line endings for
- /// Text to replace all of the line endings in
- /// string with line endings replaced by
- [MethodImpl( MethodImplOptions.AggressiveInlining )]
- [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1611:Element parameters should be documented", Justification = "Extension" )]
- public static string ReplaceLineEndings( this string self, string replacementText )
- {
- ArgumentNullException.ThrowIfNull(self);
- ArgumentNullException.ThrowIfNull(replacementText);
-
- string retVal = UnicodeNewLinesRegEx.Replace(self, replacementText);
-
- // if the result of replacement is the same, just return the original
- // This is wasted overhead, but at least matches the behavior
- return self == retVal ? self : retVal;
- }
-
- // The Unicode Standard, Sec. 5.8, Recommendation R4 and Table 5-2 state that the CR, LF,
- // CRLF, NEL, LS, FF, and PS sequences are considered newline functions. That section
- // also specifically excludes VT from the list of newline functions, so we do not include
- // it in the regular expression match.
-
- // language=regex
- private const string UnicodeNewLinesRegExPattern = @"(\r\n|\r|\n|\f|\u0085|\u2028|\u2029)";
-
- private static Regex UnicodeNewLinesRegEx { get; } = new Regex( UnicodeNewLinesRegExPattern );
- }
-}
-#endif
diff --git a/src/Ubiquity.NET.Extensions/Properties/ResourceNotFoundException.cs b/src/Ubiquity.NET.Extensions/Properties/ResourceNotFoundException.cs
new file mode 100644
index 000000000..2fe41e37d
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/Properties/ResourceNotFoundException.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.Extensions.Properties
+{
+ /// Exception thornw if a resource is missing
+ ///
+ /// This is ALWAYS a bug in the application and should not be caught or suppressed in any way.
+ /// It indicates that a named resource does not exist, either add the resource or correct the
+ /// spelling of the name - NEVER dismiss this.
+ ///
+ [Serializable]
+ public class ResourceNotFoundException
+ : Exception
+ {
+ /// Initializes a new instance of the class.
+ public ResourceNotFoundException( )
+ {
+ }
+
+ ///
+ public ResourceNotFoundException( string resourceName )
+ : base( string.Format( Resources.Culture, Resources.Missing_Resource_Exception_Message_fmt, resourceName ) )
+ {
+ ResourceName = resourceName;
+ }
+
+ ///
+ public ResourceNotFoundException( string message, Exception inner )
+ : base( message, inner )
+ {
+ }
+
+ /// Gets the name of the resource missing
+ public string ResourceName { get; } = string.Empty;
+ }
+}
diff --git a/src/Ubiquity.NET.Extensions/Properties/Resources.Designer.cs b/src/Ubiquity.NET.Extensions/Properties/Resources.Designer.cs
index d7fa71cf2..71cb072c4 100644
--- a/src/Ubiquity.NET.Extensions/Properties/Resources.Designer.cs
+++ b/src/Ubiquity.NET.Extensions/Properties/Resources.Designer.cs
@@ -149,5 +149,23 @@ internal static string ArgumentOutOfRange_Generic_MustBeNotEqual {
return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeNotEqual", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to The value of argument '{0}' ({1}) is invalid for Enum of type '{2}'.
+ ///
+ internal static string InvalidEnumArgument_NonInt {
+ get {
+ return ResourceManager.GetString("InvalidEnumArgument_NonInt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Missing resource. Name='{0}'.
+ ///
+ internal static string Missing_Resource_Exception_Message_fmt {
+ get {
+ return ResourceManager.GetString("Missing_Resource_Exception_Message_fmt", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Ubiquity.NET.Extensions/Properties/Resources.resx b/src/Ubiquity.NET.Extensions/Properties/Resources.resx
index 68a149afe..62c8ccfc7 100644
--- a/src/Ubiquity.NET.Extensions/Properties/Resources.resx
+++ b/src/Ubiquity.NET.Extensions/Properties/Resources.resx
@@ -147,4 +147,11 @@
The value cannot be an empty string or composed entirely of whitespace.
-
+
+ The value of argument '{0}' ({1}) is invalid for Enum of type '{2}'
+ Message used for enums with an underlying type that is not able to fit into an int. {0} - expression that generated the exception; {1} value of the expression that is invalid; {2} is the type of the enumeration.
+
+
+ Missing resource. Name='{0}'
+
+
\ No newline at end of file
diff --git a/src/Ubiquity.NET.Extensions/Properties/SR.cs b/src/Ubiquity.NET.Extensions/Properties/SR.cs
new file mode 100644
index 000000000..d59355747
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/Properties/SR.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.Extensions.Properties
+{
+ // RESX file generator does not use `partial` and VS2022 doesn't correctly support
+ // C# 14 `extension` so derivation is the only option at present...
+ // CONSIDER: Generator to create this from an attribute that includes the type of the Resources
+ // generator/Analyzer should validate that the type has a ResourceManager member.
+ // (Effectively a C++ concept since the type is generated by a SingleFileGenerator
+ // it doesn't have an interface or base type to use as a constraint)
+ internal static class SR
+ {
+ internal static string Format( [NotNull] string resourceName, TArg0 arg0 )
+ {
+ ArgumentNullException.ThrowIfNull(resourceName);
+
+ string? fmt = Resources.ResourceManager.GetString(resourceName, Resources.Culture) ?? throw new ResourceNotFoundException(resourceName);
+ return string.Format(Resources.Culture, fmt, arg0);
+ }
+
+ internal static string Format( [NotNull] string resourceName, TArg0 arg0, TArg1 arg1 )
+ {
+ ArgumentNullException.ThrowIfNull(resourceName);
+
+ string? fmt = Resources.ResourceManager.GetString(resourceName, Resources.Culture) ?? throw new ResourceNotFoundException(resourceName);
+ return string.Format(Resources.Culture, fmt, arg0, arg1);
+ }
+
+ internal static string Format( [NotNull] string resourceName, TArg0 arg0, TArg1 arg1, TArg2 arg2 )
+ {
+ ArgumentNullException.ThrowIfNull(resourceName);
+
+ string? fmt = Resources.ResourceManager.GetString(resourceName, Resources.Culture) ?? throw new ResourceNotFoundException(resourceName);
+ return string.Format(Resources.Culture, fmt, arg0, arg1, arg2);
+ }
+
+ internal static string Format( [NotNull] string resourceName, params IEnumerable